从实模式到保护模式笔记12题解补充
从实模式到保护模式笔记12题解补充
0x00 代码
这篇博客是上一篇博客检测题部分第三题的题解
引导代码修改如下:
;代码清单13-1
;文件名:c13_mbr.asm
;文件说明:硬盘主引导扇区代码
;创建日期:2011-10-28 22:35 ;设置堆栈段和栈指针
core_base_address equ 0x00040000 ;常数,内核加载的起始内存地址
core_start_sector equ 0x00000001 ;常数,内核的起始逻辑扇区号
mov ax,cs
mov ss,ax
mov sp,0x7c00
;计算GDT所在的逻辑段地址
mov eax,[cs:pgdt+0x7c00+0x02] ;GDT的32位物理地址
xor edx,edx
mov ebx,16
div ebx ;分解成16位逻辑地址
mov ds,eax ;令DS指向该段以进行操作
mov ebx,edx ;段内起始偏移地址
;跳过0#号描述符的槽位
;创建1#描述符,这是一个数据段,对应0~4GB的线性地址空间
mov dword [ebx+0x08],0x0000ffff ;基地址为0,段界限为0xFFFFF
mov dword [ebx+0x0c],0x00cf9200 ;粒度为4KB,存储器段描述符
;创建保护模式下初始代码段描述符
mov dword [ebx+0x10],0x0000ffff ;基地址为0x00007c00,界限0x1FF
mov dword [ebx+0x14],0x00cf9800 ;粒度为1个字节,代码段描述符
;建立保护模式下的堆栈段描述符 ;0x00006c00-0x00007C00
;建立保护模式下的显示缓冲区描述符 ;0x000b8000
;初始化描述符表寄存器GDTR
mov word [cs: pgdt+0x7c00],23 ;描述符表的界限
lgdt [cs: pgdt+0x7c00]
in al,0x92 ;南桥芯片内的端口
or al,0000_0010B
out 0x92,al ;打开A20
cli ;中断机制尚未工作
mov eax,cr0
or eax,1
mov cr0,eax ;设置PE位
;以下进入保护模式... ...
jmp dword 0x0010:flush+0x7c00 ;16位的描述符选择子:32位偏移
;清流水线并串行化处理器
[bits 32]
flush:
mov eax,0x0008 ;加载数据段(0..4GB)选择子
mov ds,eax
mov eax,0x0008 ;加载堆栈段选择子(0-4gb)
mov ss,eax
mov esp,0x7c00 ;堆栈指针 0x7c00
;以下加载系统核心程序
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 ;512字节每扇区
div ecx
or edx,edx
jnz @1 ;未除尽,因此结果比实际扇区数少1
dec eax ;已经读了一个扇区,扇区总数减1
@1:
or eax,eax ;考虑实际长度≤512个字节的情况
jz setup ;EAX=0 ?
;读取剩余的扇区
mov ecx,eax ;32位模式下的LOOP使用ECX
mov eax,core_start_sector
inc eax ;从下一个逻辑扇区接着读
@2:
call read_hard_disk_0
inc eax
loop @2 ;循环读,直到读完整个内核
setup:
;啥描述符都不用新建,毕竟平坦模型都是0-4gb 而0-4的代码和
;数据段都已经安装好了 跳转就是了
push 0x10
mov edi,core_base_address+0x04
mov edi,[edi]
add edi,core_base_address
push edi
retf
;-------------------------------------------------------------------------------
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
.waits:
in al,dx
and al,0x88
cmp al,0x08
jnz .waits ;不忙,且硬盘已准备好数据传输
mov ecx,256 ;总共要读取的字数
mov dx,0x1f0
.readw:
in ax,dx
mov [ebx],ax
add ebx,2
loop .readw
pop edx
pop ecx
pop eax
ret
;-------------------------------------------------
pgdt dw 0
dd 0x00007e00 ;GDT的物理地址
;-------------------------------------------------------------------------------
times 510-($-$$) db 0
db 0x55,0xaa
内核主体代码:
core_data_seg_sel equ 0x08
core_code_seg_sel equ 0x10
core_base_address equ 0x80040000
core_base_address_old equ 0x00040000
core_length dd core_end
core_entry dd start
[bits 32]
;--------------
put_string:;ds:ebx 串
push ecx
.getc:
mov cl,[ebx]
or cl,cl
jz .exit
call put_char
inc ebx
jmp .getc
.exit:
pop ecx
retf;段间转移
;---------------
put_char:
pushad
mov dx,0x3d4
mov al,0x0e
out dx,al
inc dx ;0x3d5
in al,dx ;高字
mov ah,al
dec dx ;0x3d4
mov al,0x0f
out dx,al
inc dx ;0x3d5
in al,dx ;低字
mov bx,ax ;BX=代表光标位置的16位数
cmp cl,0x0d ;回车符?
jnz .put_0a
mov ax,bx
mov bl,80
div bl
mul bl
mov bx,ax
jmp .set_cursor
.put_0a:
cmp cl,0x0a ;换行符?
jnz .put_other
add bx,80
jmp .roll_screen
.put_other:
push es
mov eax,core_data_seg_sel
mov es,eax
shl bx,1
push eax
movzx eax,bx
add eax,0x800b8000
mov [es:eax],cl
pop eax
pop es
shr bx,1
inc bx
.roll_screen:
cmp bx,2000
jl .set_cursor
push ds
push es
mov eax,core_data_seg_sel
mov ds,eax
mov es,eax
cld
mov esi,0xa0+0x800b8000
mov edi,0x00+0x800b8000
mov ecx,1920
rep movsw
mov bx,3840
mov ecx,80
.cls:
push eax
movzx eax,bx
add eax,0x800b8000
mov word [es:eax],0x0720
pop eax
add bx,2
loop .cls
pop es
pop ds
mov bx,1920
.set_cursor:
mov dx,0x3d4
mov al,0x0e
out dx,al
inc dx ;0x3d5
mov al,bh
out dx,al
dec dx ;0x3d4
mov al,0x0f
out dx,al
inc dx ;0x3d5
mov al,bl
out dx,al
popad
ret;段内转移
;-----------
read_hard_disk_0:;eax 扇区 ds 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
.waits:
in al,dx
and al,0x88
cmp al,0x08
jnz .waits ;不忙,且硬盘已准备好数据传输
mov ecx,256 ;总共要读取的字数
mov dx,0x1f0
.readw:
in ax,dx
mov [ebx],ax
add ebx,2
loop .readw
pop edx
pop ecx
pop eax
retf
;------------
put_hex_dword: ;在当前光标处以十六进制形式显示
;一个双字并推进光标
;输入:EDX=要转换并显示的数字
;输出:无
pushad
push ds
mov ax,core_data_seg_sel ;切换到核心数据段
mov ds,ax
mov ebx,bin_hex+core_base_address ;指向核心数据段内的转换表
mov ecx,8
.xlt:
rol edx,4
mov eax,edx
and eax,0x0000000f
xlat
push ecx
mov cl,al
call put_char
pop ecx
loop .xlt
pop ds
popad
retf
;---------------
set_up_gdt_descriptor: ;在GDT内安装一个新的描述符
;输入:EDX:EAX=描述符
;输出:CX=描述符的选择子
push eax
push ebx
push edx
push ds
push es
mov ebx,core_data_seg_sel ;切换到核心数据段
mov ds,ebx
mov es,ebx
push eax
mov eax,core_base_address
sgdt [pgdt+eax] ;以便开始处理GDT
pop eax
push eax
mov eax,core_base_address
movzx ebx,word [pgdt+eax] ;GDT界限
inc bx ;GDT总字节数,也是下一个描述符偏移
add ebx,[pgdt+2+eax] ;下一个描述符的线性地址
pop eax
mov [es:ebx],eax
mov [es:ebx+4],edx
push eax
mov eax,core_base_address
add word [pgdt+eax],8 ;增加一个描述符的大小
lgdt [pgdt+eax] ;对GDT的更改生效
pop eax
push ebx
mov ebx,core_base_address
mov ax,[pgdt+ebx] ;得到GDT界限值
pop ebx
xor dx,dx
mov bx,8
div bx ;除以8,去掉余数
mov cx,ax
shl cx,3 ;将索引号移到正确位置
pop es
pop ds
pop edx
pop ebx
pop eax
retf
;------------------------------
make_seg_descriptor: ;构造存储器和系统的段描述符
;输入:EAX=线性基地址
; EBX=段界限
; ECX=属性。各属性位都在原始
; 位置,无关的位清零
;返回:EDX:EAX=描述符
mov edx,eax
shl eax,16
or ax,bx ;描述符前32位(EAX)构造完毕
and edx,0xffff0000 ;清除基地址中无关的位
rol edx,8
bswap edx ;装配基址的31~24和23~16 (80486+)
xor bx,bx
or edx,ebx ;装配段界限的高4位
or edx,ecx ;装配属性
retf
;------------------------
make_gate_descriptor: ;构造门的描述符(调用门等)
;输入:EAX=门代码在段内偏移地址
; BX=门代码所在段的选择子
; CX=段类型及属性等(各属
; 性位都在原始位置)
;返回:EDX:EAX=完整的描述符
push ebx
push ecx
mov edx,eax
and edx,0xffff0000 ;得到偏移地址高16位
or dx,cx ;组装属性部分到EDX
and eax,0x0000ffff ;得到偏移地址低16位
shl ebx,16
or eax,ebx ;组装段选择子部分
pop ecx
pop ebx
retf
;-------------
allocate_a_4k_page: ;分配一个4KB的页
;输入:无
;输出:EAX=页的物理地址
push ebx
push ecx
push edx
push ds
mov eax,core_data_seg_sel
mov ds,eax
xor eax,eax
.b1:
push ebx
mov ebx,core_base_address
bts [page_bit_map+ebx],eax
pop ebx
jnc .b2
inc eax
cmp eax,page_map_len*8;page_map_len是equ常量
jl .b1
mov ebx,message_3+core_base_address
call core_code_seg_sel:put_string+core_base_address
hlt ;没有可以分配的页,停机
.b2:
shl eax,12 ;乘以4096(0x1000)
pop ds
pop edx
pop ecx
pop ebx
ret
;--------------
alloc_inst_a_page: ;分配一个页,并安装在当前活动的
;层级分页结构中
;输入:EBX=页的线性地址
push eax
push ebx
push esi
push ds
mov eax,core_data_seg_sel
mov ds,eax
;检查该线性地址所对应的页表是否存在
mov esi,ebx
and esi,0xffc00000
shr esi,20 ;得到页目录索引,并乘以4
or esi,0xfffff000 ;页目录自身的线性地址+表内偏移
test dword [esi],0x00000001 ;P位是否为“1”。检查该线性地址是
jnz .b1 ;否已经有对应的页表
;创建该线性地址所对应的页表
call allocate_a_4k_page ;分配一个页做为页表
or eax,0x00000007
mov [esi],eax ;在页目录中登记该页表
.b1:
;开始访问该线性地址所对应的页表
mov esi,ebx
shr esi,10
and esi,0x003ff000 ;或者0xfffff000,因高10位是零
or esi,0xffc00000 ;得到该页表的线性地址
;得到该线性地址在页表内的对应条目(页表项)
and ebx,0x003ff000
shr ebx,10 ;相当于右移12位,再乘以4
or esi,ebx ;页表项的线性地址
call allocate_a_4k_page ;分配一个页,这才是要安装的页
or eax,0x00000007
mov [esi],eax
pop ds
pop esi
pop ebx
pop eax
retf
;----------
create_copy_cur_pdir: ;创建新页目录,并复制当前页目录内容
;输入:无
;输出:EAX=新页目录的物理地址
push ds
push es
push esi
push edi
push ebx
push ecx
mov ebx,core_data_seg_sel
mov ds,ebx
mov es,ebx
call allocate_a_4k_page
mov ebx,eax
or ebx,0x00000007
push eax
mov eax,0xfffffff8
mov [eax],ebx
pop eax
mov esi,0xfffff000 ;ESI->当前页目录的线性地址
mov edi,0xffffe000 ;EDI->新页目录的线性地址
mov ecx,1024 ;ECX=要复制的目录项数
cld
repe movsd
pop ecx
pop ebx
pop edi
pop esi
pop es
pop ds
retf
;---------
terminate_current_task: ;终止当前任务
;注意,执行此例程时,当前任务仍在
;运行中。此例程其实也是当前任务的
;一部分
mov eax,core_data_seg_sel
mov ds,eax
pushfd
pop edx
test dx,0100_0000_0000_0000B ;测试NT位
jnz .b1 ;当前任务是嵌套的,到.b1执行iretd
push eax
mov eax,core_base_address
jmp far [program_man_tss+eax] ;程序管理器任务
pop eax
retf
.b1:
iretd
retf
;-------------------------
;数据
pgdt dw 0 ;用于设置和修改GDT
dd 0
page_bit_map db 0xff,0xff,0xff,0xff,0xff,0x55,0x55,0xff
db 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
db 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
db 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
db 0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55
db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
page_map_len equ $-page_bit_map
;符号地址检索表
salt:
salt_1 db '@PrintString'
times 256-($-salt_1) db 0
dd put_string+core_base_address;偏移地址要改,因为基地址是0x00000000了
dw core_code_seg_sel
salt_2 db '@ReadDiskData'
times 256-($-salt_2) db 0
dd read_hard_disk_0+core_base_address
dw core_code_seg_sel
salt_3 db '@PrintDwordAsHexString'
times 256-($-salt_3) db 0
dd put_hex_dword+core_base_address
dw core_code_seg_sel
salt_4 db '@TerminateProgram'
times 256-($-salt_4) db 0
dd terminate_current_task+core_base_address;偏移地址要改
dw core_code_seg_sel
salt_item_len equ $-salt_4
salt_items equ ($-salt)/salt_item_len
message_0 db ' Working in system core,protect mode.'
db 0x0d,0x0a,0
message_1 db ' Paging is enabled.System core is mapped to'
db ' address 0x80000000.',0x0d,0x0a,0
message_2 db 0x0d,0x0a
db ' System wide CALL-GATE mounted.',0x0d,0x0a,0
message_3 db '********No more pages********',0
message_4 db 0x0d,0x0a,' Task switching...@_@',0x0d,0x0a,0
message_5 db 0x0d,0x0a,' Processor HALT.',0
bin_hex db '0123456789ABCDEF'
;put_hex_dword子过程用的查找表
core_buf times 512 db 0 ;内核用的缓冲区
cpu_brnd0 db 0x0d,0x0a,' ',0
cpu_brand times 52 db 0
cpu_brnd1 db 0x0d,0x0a,0x0d,0x0a,0
;任务控制块链
tcb_chain dd 0
;内核信息
core_next_laddr dd 0x80100000 ;内核空间中下一个可分配的线性地址
program_man_tss dd 0 ;程序管理器的TSS描述符选择子
dw 0
;--------------
;代码
;-------------
fill_descriptor_in_ldt: ;在LDT内安装一个新的描述符
;输入:EDX:EAX=描述符
; EBX=TCB基地址
;输出:CX=描述符的选择子
push eax
push edx
push edi
push ds
mov ecx,core_data_seg_sel
mov ds,ecx
mov edi,[ebx+0x0c] ;获得LDT基地址
xor ecx,ecx
mov cx,[ebx+0x0a] ;获得LDT界限
inc cx ;LDT的总字节数,即新描述符偏移地址
mov [edi+ecx+0x00],eax
mov [edi+ecx+0x04],edx ;安装描述符
add cx,8
dec cx ;得到新的LDT界限值
mov [ebx+0x0a],cx ;更新LDT界限值到TCB
mov ax,cx
xor dx,dx
mov cx,8
div cx
mov cx,ax
shl cx,3 ;左移3位,并且
or cx,0000_0000_0000_0100B ;使TI位=1,指向LDT,最后使RPL=00
pop ds
pop edi
pop edx
pop eax
ret
;------------
load_relocate_program: ;加载并重定位用户程序
;输入: PUSH 逻辑扇区号
; PUSH 任务控制块基地址
;输出:无
pushad
;40ab0
push ds
push es
mov ebp,esp ;为访问通过堆栈传递的参数做准备
mov ecx,core_data_seg_sel
mov es,ecx
;清空当前页目录表的前半部分(对应低2GB的局部地址空间)
mov ebx,0xfffff000
xor esi,esi
.b1:
mov dword [es:ebx+esi*4],0x00000000
inc esi
cmp esi,512
jl .b1
; mov ebx,0xffe00400
; xor esi,esi
; .bb1:
; mov dword [es:0xffe00400+esi*4],0x00000000
; inc esi
; cmp esi,768
; jl .bb1
;以下开始分配内存并加载用户程序
mov eax,core_data_seg_sel
mov ds,eax ;切换DS到内核数据段
mov eax,[ebp+12*4] ;从堆栈中取出用户程序起始扇区号
mov ebx,core_buf+core_base_address ;读取程序头部数据
call core_code_seg_sel:read_hard_disk_0+core_base_address
;以下判断整个程序有多大
mov eax,[core_buf+core_base_address] ;程序尺寸
mov ebx,eax
and ebx,0xfffff000 ;使之4KB对齐
add ebx,0x1000
test eax,0x00000fff ;程序的大小正好是4KB的倍数吗?
cmovnz eax,ebx ;不是。使用凑整的结果
mov ecx,eax
shr ecx,12 ;程序占用的总4KB页数
mov eax,core_data_seg_sel ;切换DS到0-4GB的段
mov ds,eax
mov eax,[ebp+12*4] ;起始扇区号
mov esi,[ebp+11*4] ;从堆栈中取得TCB的基地址
.b2:
mov ebx,[es:esi+0x06] ;取得可用的线性地址
add dword [es:esi+0x06],0x1000
call core_code_seg_sel:alloc_inst_a_page+core_base_address
push ecx
mov ecx,8
.b3:
call core_code_seg_sel:read_hard_disk_0+core_base_address
inc eax
loop .b3
pop ecx
loop .b2
;在内核地址空间内创建用户任务的TSS
mov eax,core_data_seg_sel ;切换DS到内核数据段
mov ds,eax
push eax
mov eax,core_base_address
mov ebx,[core_next_laddr+eax] ;用户任务的TSS必须在全局空间上分配
call core_code_seg_sel:alloc_inst_a_page+core_base_address
add dword [core_next_laddr+eax],4096
pop eax
mov [es:esi+0x14],ebx ;在TCB中填写TSS的线性地址
mov word [es:esi+0x12],103 ;在TCB中填写TSS的界限值
;在用户任务的局部地址空间内创建LDT
mov ebx,[es:esi+0x06] ;从TCB中取得可用的线性地址
add dword [es:esi+0x06],0x1000
call core_code_seg_sel:alloc_inst_a_page+core_base_address
mov [es:esi+0x0c],ebx ;填写LDT线性地址到TCB中
;建立程序代码段描述符
mov eax,0x00000000
mov ebx,0x000fffff
mov ecx,0x00c0f800 ;4KB粒度的代码段描述符,特权级3
call core_code_seg_sel:make_seg_descriptor+core_base_address
mov ebx,esi ;TCB的基地址
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0011B ;设置选择子的特权级为3
mov ebx,[es:esi+0x14] ;从TCB中获取TSS的线性地址
mov [es:ebx+76],cx ;填写TSS的CS域
;建立程序数据段描述符
mov eax,0x00000000
mov ebx,0x000fffff
mov ecx,0x00c0f200 ;4KB粒度的数据段描述符,特权级3
call core_code_seg_sel:make_seg_descriptor+core_base_address
mov ebx,esi ;TCB的基地址
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0011B ;设置选择子的特权级为3
mov ebx,[es:esi+0x14] ;从TCB中获取TSS的线性地址
mov [es:ebx+84],cx ;填写TSS的DS域
mov [es:ebx+72],cx ;填写TSS的ES域
mov [es:ebx+88],cx ;填写TSS的FS域
mov [es:ebx+92],cx ;填写TSS的GS域
;将数据段作为用户任务的3特权级固有堆栈
mov ebx,[es:esi+0x06] ;从TCB中取得可用的线性地址
add dword [es:esi+0x06],0x1000
call core_code_seg_sel:alloc_inst_a_page+core_base_address
mov ebx,[es:esi+0x14] ;从TCB中获取TSS的线性地址
mov [es:ebx+80],cx ;填写TSS的SS域
mov edx,[es:esi+0x06] ;堆栈的高端线性地址
mov [es:ebx+56],edx ;填写TSS的ESP域
;在用户任务的局部地址空间内创建0特权级堆栈
mov ebx,[es:esi+0x06] ;从TCB中取得可用的线性地址
add dword [es:esi+0x06],0x1000
call core_code_seg_sel:alloc_inst_a_page+core_base_address
mov eax,0x00000000
mov ebx,0x000fffff
mov ecx,0x00c09200 ;4KB粒度的堆栈段描述符,特权级0
call core_code_seg_sel:make_seg_descriptor+core_base_address
mov ebx,esi ;TCB的基地址
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0000B ;设置选择子的特权级为0
mov ebx,[es:esi+0x14] ;从TCB中获取TSS的线性地址
mov [es:ebx+8],cx ;填写TSS的SS0域
mov edx,[es:esi+0x06] ;堆栈的高端线性地址
mov [es:ebx+4],edx ;填写TSS的ESP0域
;在用户任务的局部地址空间内创建1特权级堆栈
mov ebx,[es:esi+0x06] ;从TCB中取得可用的线性地址
add dword [es:esi+0x06],0x1000
call core_code_seg_sel:alloc_inst_a_page+core_base_address
mov eax,0x00000000
mov ebx,0x000fffff
mov ecx,0x00c0b200 ;4KB粒度的堆栈段描述符,特权级1
call core_code_seg_sel:make_seg_descriptor+core_base_address
mov ebx,esi ;TCB的基地址
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0001B ;设置选择子的特权级为1
mov ebx,[es:esi+0x14] ;从TCB中获取TSS的线性地址
mov [es:ebx+16],cx ;填写TSS的SS1域
mov edx,[es:esi+0x06] ;堆栈的高端线性地址
mov [es:ebx+12],edx ;填写TSS的ESP1域
;在用户任务的局部地址空间内创建2特权级堆栈
mov ebx,[es:esi+0x06] ;从TCB中取得可用的线性地址
add dword [es:esi+0x06],0x1000
call core_code_seg_sel:alloc_inst_a_page+core_base_address
mov eax,0x00000000
mov ebx,0x000fffff
mov ecx,0x00c0d200 ;4KB粒度的堆栈段描述符,特权级2
call core_code_seg_sel:make_seg_descriptor+core_base_address
mov ebx,esi ;TCB的基地址
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0010B ;设置选择子的特权级为2
mov ebx,[es:esi+0x14] ;从TCB中获取TSS的线性地址
mov [es:ebx+24],cx ;填写TSS的SS2域
mov edx,[es:esi+0x06] ;堆栈的高端线性地址
mov [es:ebx+20],edx ;填写TSS的ESP2域
;重定位SALT
mov eax,core_data_seg_sel ;访问任务的4GB虚拟地址空间时用
mov es,eax
mov ds,eax
cld
mov ecx,[es:0x0c] ;U-SALT条目数
mov edi,[es:0x08] ;U-SALT在4GB空间内的偏移
.b4:
push ecx
push edi
mov ecx,salt_items;equ常量定义
mov esi,salt+core_base_address
.b5:
push edi
push esi
push ecx
mov ecx,64 ;检索表中,每条目的比较次数
repe cmpsd ;每次比较4字节
jnz .b6
mov eax,[esi] ;若匹配,则esi恰好指向其后的地址
mov [es:edi-256],eax ;将字符串改写成偏移地址
mov ax,[esi+4]
or ax,0000000000000011B ;以用户程序自己的特权级使用调用门
;故RPL=3
mov [es:edi-252],ax ;回填调用门选择子
.b6:
pop ecx
pop esi
add esi,salt_item_len;equ常量
pop edi ;从头比较
loop .b5
pop edi
add edi,256
pop ecx
loop .b4
;在GDT中登记LDT描述符
mov esi,[ebp+11*4] ;从堆栈中取得TCB的基地址
mov eax,[es:esi+0x0c] ;LDT的起始线性地址
movzx ebx,word [es:esi+0x0a] ;LDT段界限
mov ecx,0x00408200 ;LDT描述符,特权级0
call core_code_seg_sel:make_seg_descriptor+core_base_address
call core_code_seg_sel:set_up_gdt_descriptor+core_base_address
mov [es:esi+0x10],cx ;登记LDT选择子到TCB中
mov ebx,[es:esi+0x14] ;从TCB中获取TSS的线性地址
mov [es:ebx+96],cx ;填写TSS的LDT域
mov word [es:ebx+0],0 ;反向链=0
mov dx,[es:esi+0x12] ;段长度(界限)
mov [es:ebx+102],dx ;填写TSS的I/O位图偏移域
mov word [es:ebx+100],0 ;T=0
mov eax,[es:0x04] ;从任务的4GB地址空间获取入口点
mov [es:ebx+32],eax ;填写TSS的EIP域
pushfd
pop edx
mov [es:ebx+36],edx ;填写TSS的EFLAGS域
;在GDT中登记TSS描述符
mov eax,[es:esi+0x14] ;从TCB中获取TSS的起始线性地址
movzx ebx,word [es:esi+0x12] ;段长度(界限)
mov ecx,0x00408900 ;TSS描述符,特权级0
call core_code_seg_sel:make_seg_descriptor+core_base_address
call core_code_seg_sel:set_up_gdt_descriptor+core_base_address
mov [es:esi+0x18],cx ;登记TSS选择子到TCB
;创建用户任务的页目录
;注意!页的分配和使用是由页位图决定的,可以不占用线性地址空间
call core_code_seg_sel:create_copy_cur_pdir+core_base_address
mov ebx,[es:esi+0x14] ;从TCB中获取TSS的线性地址
mov dword [es:ebx+28],eax ;填写TSS的CR3(PDBR)域
pop es ;恢复到调用此过程前的es段
pop ds ;恢复到调用此过程前的ds段
popad
ret 8 ;丢弃调用本过程前压入的参数
;-----------
append_to_tcb_link: ;在TCB链上追加任务控制块
;输入:ECX=TCB线性基地址
push eax
push edx
push ds
push es
mov eax,core_data_seg_sel ;令DS指向内核数据段
mov ds,eax
mov eax,core_data_seg_sel ;令ES指向0..4GB段
mov es,eax
mov dword [es: ecx+0x00],0 ;当前TCB指针域清零,以指示这是最
;后一个TCB
mov eax,[tcb_chain+core_base_address] ;TCB表头指针
or eax,eax ;链表为空?
jz .notcb
.searc:
mov edx,eax
mov eax,[es: edx+0x00]
or eax,eax
jnz .searc
mov [es: edx+0x00],ecx
jmp .retpc
.notcb:
push eax
mov eax,core_base_address
mov [tcb_chain+eax],ecx ;若为空表,直接令表头指针指向TCB
pop eax
.retpc:
pop es
pop ds
pop edx
pop eax
ret
;--------
start:
mov ecx,core_data_seg_sel ;令DS指向核心数据段
mov ds,ecx
mov ecx,core_data_seg_sel ;令ES指向4GB数据段
mov es,ecx
;显示处理器品牌信息
;被省略了
;准备打开分页机制
;创建系统内核的页目录表PDT
;页目录表清零
mov ecx,1024 ;1024个目录项
mov ebx,0x00020000 ;页目录的物理地址
xor esi,esi
.b1:
mov dword [es:ebx+esi],0x00000000 ;页目录表项清零
add esi,4
loop .b1
;在页目录内创建指向页目录自己的目录项
mov dword [es:ebx+4092],0x00020003
;在页目录内创建与线性地址0x00000000对应的目录项
mov dword [es:ebx+0],0x00021003 ;写入目录项(页表的物理地址和属性)
;创建与上面那个目录项相对应的页表,初始化页表项
mov ebx,0x00021000 ;页表的物理地址
xor eax,eax ;起始页的物理地址
xor esi,esi
.b2:
mov edx,eax
or edx,0x00000003
mov [es:ebx+esi*4],edx ;登记页的物理地址
add eax,0x1000 ;下一个相邻页的物理地址
inc esi
cmp esi,256 ;仅低端1MB内存对应的页才是有效的
jl .b2
.b3: ;其余的页表项置为无效
mov dword [es:ebx+esi*4],0x00000000
inc esi
cmp esi,1024
jl .b3
;令CR3寄存器指向页目录,并正式开启页功能
mov eax,0x00020000 ;PCD=PWT=0
mov cr3,eax
;现在的映射是 0x00000000-0x000fffff ->0x00000000-0x000fffff
mov eax,cr0
or eax,0x80000000
mov cr0,eax ;开启分页机制
;在页目录内创建与线性地址0x80000000对应的目录项
mov ebx,0xfffff000 ;页目录自己的线性地址
mov esi,0x80000000 ;映射的起始地址
shr esi,22 ;线性地址的高10位是目录索引
shl esi,2
mov dword [es:ebx+esi],0x00021003 ;写入目录项(页表的物理地址和属性)
;目标单元的线性地址为0xFFFFF200
push eax
push ebx
mov eax,core_base_address_old
sgdt [pgdt+eax]
mov ebx,[pgdt+eax+2]
add dword [pgdt+eax+2],0x80000000 ;GDTR也用的是线性地址
lgdt [pgdt+eax]
sgdt [pgdt+eax]
mov ebx,[pgdt+eax+2]
pop ebx
pop eax
;建立了新映射后,要用新的映射(线性地址)去访问代码数据堆栈
;所以要更新EIP ESP
push 0x10
mov eax,core_base_address
add eax,flush
push eax
retf
;
flush:
mov eax,core_data_seg_sel
mov ss,eax
mov ebp,esp
add ebp,0x80000000
mov esp,ebp
mov eax,core_data_seg_sel
mov ds,eax
mov ebx,message_1+core_base_address
call core_code_seg_sel:put_string+core_base_address
;以下开始安装为整个系统服务的调用门。特权级之间的控制转移必须使用门
mov edi,salt+core_base_address ;C-SALT表的起始位置
mov ecx,salt_items ;C-SALT表的条目数量
.b4:
push ecx
mov eax,[edi+256] ;该条目入口点的32位偏移地址
mov bx,[edi+260] ;该条目入口点的段选择子
mov cx,1_11_0_1100_000_00000B ;特权级3的调用门(3以上的特权级才
;允许访问),0个参数(因为用寄存器
;传递参数,而没有用栈)
call core_code_seg_sel:make_gate_descriptor+core_base_address
call core_code_seg_sel:set_up_gdt_descriptor+core_base_address
mov [edi+260],cx ;将返回的门描述符选择子回填
add edi,salt_item_len ;指向下一个C-SALT条目
pop ecx
loop .b4
;对门进行测试
mov ebx,message_2+core_base_address
push eax
mov eax,core_base_address
call far [salt_1+256+eax] ;通过门显示信息(偏移量将被忽略)
;为程序管理器的TSS分配内存空间
mov ebx,[core_next_laddr+eax]
call core_code_seg_sel:alloc_inst_a_page+core_base_address
add dword [core_next_laddr+eax],4096
pop eax
;在程序管理器的TSS中设置必要的项目
mov word [es:ebx+0],0 ;反向链=0
mov eax,cr3
mov dword [es:ebx+28],eax ;登记CR3(PDBR)
mov word [es:ebx+96],0 ;没有LDT。处理器允许没有LDT的任务。
mov word [es:ebx+100],0 ;T=0
mov word [es:ebx+102],103 ;没有I/O位图。0特权级事实上不需要。
;创建程序管理器的TSS描述符,并安装到GDT中
mov eax,ebx ;TSS的起始线性地址
mov ebx,103 ;段长度(界限)
mov ecx,0x00408900 ;TSS描述符,特权级0
call core_code_seg_sel:make_seg_descriptor+core_base_address
call core_code_seg_sel:set_up_gdt_descriptor+core_base_address
push eax
mov eax,core_base_address
mov [program_man_tss+4+eax],cx ;保存程序管理器的TSS描述符选择子
pop eax
;任务寄存器TR中的内容是任务存在的标志,该内容也决定了当前任务是谁。
;下面的指令为当前正在执行的0特权级任务“程序管理器”后补手续(TSS)。
ltr cx
;现在可认为“程序管理器”任务正执行中
;创建用户任务的任务控制块
push eax
mov eax,core_base_address
mov ebx,[core_next_laddr+eax]
call core_code_seg_sel:alloc_inst_a_page+core_base_address
add dword [core_next_laddr+eax],4096
pop eax
mov dword [es:ebx+0x06],0 ;用户任务局部空间的分配从0开始。
mov word [es:ebx+0x0a],0xffff ;登记LDT初始的界限到TCB中
mov ecx,ebx
call append_to_tcb_link ;将此TCB添加到TCB链中
push dword 50 ;用户程序位于逻辑50扇区
push ecx ;压入任务控制块起始线性地址
call load_relocate_program
mov ebx,message_4+core_base_address
call core_code_seg_sel:put_string+core_base_address
call far [es:ecx+0x14] ;执行任务切换。
mov ebx,message_5+core_base_address
call core_code_seg_sel:put_string+core_base_address
hlt
core_end:
上述代码编译通过 运行正确!
0x01 思路
主要有这几个坑点,首先不分段情况下,因为段基地址是0x00000000,那么mbr将内核装载到0x00040000后,跳转到内核执行,因此所有靠标号获取偏移地址的,都要再加上0x00040000。
其次,开启分页后,先构造一个将线性空间低1MB映射到物理空间低1MB的页的PDT,如果不是这样的映射,那么一旦cr0的最高位置1,开启分页机制,那么EIP ESP 立马就会报错,因为他们还是按照原来的物理地址的值改变,没来得及转换成对应线性地址就加载新的指令和进行出栈入栈了,必然会直接报错。
在已经有线性空间低1MB和物理空间低1MB映射的情况下,开始讲0x80000000,也就是高2GB线性空间开始的1MB 也映射到物理空间的低1MB,这么做是为了后面加载程序,并把内核任务的PDT复制给用户任务,实现用户任务的线性空间能访问到全局空间。
同样还是拿到PDT的第1000_0000_00项(0x80000000的最高10位)的线性地址(要计算下,前一篇博客有计算的方法,不再介绍)。通过线性地址将该项指向物理地址0x00021000的页表,即添加了新映射。
我们添加了新映射后,为了安全起见,要立马用新映射也就是0x80000000,开始的线性空间访问内核的代码、数据和栈。(因为旧映射等会在加载用户程序时会被清除,不再可用)。
在16章原来代码用的多段模型下,这很简单,只需要把 各个段(代码 数据 栈 )的段基地址和GDTR里的GDT的基地址都加上0x80000000就行了。但是现在是平坦模型了,段基地址总是万年不变的0x00000000,所以我们需要修改EIP ESP 和每一处访问线性空间的代码,给他们都加上0x80000000(不论是用标号的汇编地址还是之前数据段里定义的线性地址),比如物理地址0xb8000,在老映射里还是0xb8000,而新映射要+0x800000000。
而对于EIP的修改 我使用的先push cs 再 push 新的eip再retf虚拟返回。ESP就直接复制一份到EBP,修改一份再复制回去。而对于大量的地址要+0x80000000,我用equ定义常量core_base_address_old 0x00040000,用来表示旧映射关系下的偏移 用core_base_address 0x80040000,来表示新映射关系下的偏移。