从实模式到保护模式笔记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:

QQ截图20200724171256

​ 上述代码编译通过 运行正确!

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,来表示新映射关系下的偏移。