【5.5节】切换堆栈和GDT分析

来源:互联网 发布:程序化交易软件试用 编辑:程序博客网 时间:2024/04/29 20:49

    结束了boot和loader,总算进入到了kernel,由于在loader中还留着栈空间和GDT,我们想把它们移到内核,以便于管理和保护。

     值得高兴的是,我们可以用C了,可以减轻一些被汇编的晦涩的折磨。

;kernel.asm

extern Gdt_Ptr
extern Copy_GDT


Selector_Flat_C equ 16

Selector_Flat_RW equ 8

Selector_Video equ 40

[section .bss]

Stack_Space: rebs 2 * 1024

Top_Of_Stack

[section .text]


global _start

_start:
 
 mov ax,Selector_Flat_RW
 mov ds,ax
 mov es,ax

 mov ss,ax
 mov esp,Top_Of_Stack

 sgdt [Gdt_Ptr]
 call Copy_GDT
 lgdt [Gdt_Ptr]
 jmp  Selector_Flat_C:_test
_test:
 mov ax,Selector_Video
 mov gs,ax
 mov ah,0ch
 mov al,99
 mov [gs:(80 * 15) * 2)],ax
 hlt 

    在这里我跟书中的作者不一样,在书中作者在LOADER中没有特别在GDT中安排堆栈段和数据段,原来的GDT请参看loader.asm,直接用Selector_Flat_RW寻址,而在我自己的实现中加入了这两个段,所以在程序的入口必须把ds,es,ss都设置为Selector_Flat_RW,这样就可以在LINUX下编译链接好的ELF格式的汇编和C正确的寻址。在链接的时候命令行中加了这么一个参数:-Ttext 0x30400,简单的类比就是相当于NASM的ORG 30400H.

    接下来划出一个SECTION,大小为2K,用来当作栈空间,把栈顶送ESP,这样就完成一个任务。

    再下来用SGDT指令保存原来的GDTR中的值,保存在Gdt_Ptr,这是在哪个地儿呢,现在还不知道,但用EXTERN命令说明我们保证在链接的时候会找到Gdt_Ptr这个地儿来存放。

    接着就是调用一个函数把在LOADER中的GDT拷贝到内核空间里,这个函数在本KERNEL.ASM中没有写,因为我们把这个函数用C语言写了,同样用EXTERN声明一下,让我们跟随着程序来到盼望已久的C文件:cstart.c

cstart.c

Code:
  1. typedef unsigned int  u32;   
  2. typedef unsigned short  u16;   
  3. typedef unsigned char  u8;   
  4.   
  5. /* 存储段描述符/系统段描述符 */  
  6. typedef struct s_descriptor  /* 共 8 个字节 */  
  7. {   
  8.  u16 limit_low;  /* Limit */  
  9.  u16 base_low;  /* Base */  
  10.  u8 base_mid;  /* Base */  
  11.  u8 attr1;   /* P(1) DPL(2) DT(1) TYPE(4) */  
  12.  u8 limit_high_attr2; /* G(1) D(1) 0(1) AVL(1) LimitHigh(4) */  
  13.  u8 base_high;  /* Base */  
  14. }DESCRIPTOR;   
  15.   
  16. void Memory_Copy(void *src,void *des,int len);   
  17.   
  18. DESCRIPTOR GDT[1024];   
  19. char Gdt_Ptr[6];   
  20.   
  21. void Copy_GDT()   
  22. {   
  23.  Memory_Copy(*(u32*)(&Gdt_Ptr[2]),&GDT,*(u16*)(&Gdt_Ptr[0]) + 1);   
  24.  *(u16*)(&Gdt_Ptr[0]) = 1024 * sizeof(DESCRIPTOR);   
  25.  *(u32*)(&Gdt_Ptr[2]) = (u32)(&GDT);   
  26. }   

    首先typedef 3个东东,为了一眼扫过去就能看清一个数是几位的。然后定义一个8个字节的DESCRIPTOR结构,这个也无需赘言。然后是Memory_Copy函数的声明,此函数用汇编写成,等会再说。接下来定义1个DESCRIPTOR的结构数组,就是在内核中放置GDT的空间。最后就是在kernel.asm中用到的Gdt_Ptr,是个6字节的空间。

    最后在kernel.asm中调用的Copy_Gdt函数了,首先调用Memory_Copy,参数1是源指针,即使原先GDTR寄存器中指示的GDT的首地址,此地址存放在Gdt_Ptr[2]的连续4个字节中,就需要首先把&Gdt_Ptr强制转换为一个32位的指针,再解引用;参数2是目的指针,直接把GDT的地址传进去;最后是需要拷贝的GDT的字节数,此数据存放在Gdt_Ptr[0]的连续2个字节,也需要强制转换,成为16位的指针。

    拷贝完成后,把新的,在内核中的GDT的地址,和GDT的字节数填充到Gdt_Ptr,使之成为新的将要填充到GDTR中的值,代码也清楚,也无需赘言。

    执行完毕后回到kernel.asm,用lgdt指令来加载新的数据到GDTR中,此时切换GDT的任务也完成了。我们就要测试一下切换是否成功了,用一个jmp来跳转到_test处,可即使成功了还不会有任何反馈。我们在向显存中写入一个c字母,GDT中表示显存的段在GDT中的偏移为40,把它送入gs中,这样如果成功,就会有反馈了。

    附上memory_copy的代码:

 

global Memory_Copy

;Memory_Copy----------------------------------------------------------
;C函数 Mem_Copy(void *esi,void *edi,len ecx)
;ds:esi -> es:edi
;调用前默认ds,es已经填入
Memory_Copy:
 push ebp
 mov ebp,esp
 
 push ebx
 push ecx
 push esi
 push edi
 
 mov ecx,[ebp + 16]
 mov edi,[ebp + 12]
 mov esi,[ebp + 8]

.1:
 cmp ecx,0
 je .2
 mov byte bl,[ds:esi]
 inc esi
 
 mov byte [es:edi],bl
 inc edi
 
 dec ecx
 jmp .1
 
 .2:
 
 pop edi
 pop esi
 pop ecx
 pop ebx
 pop ebp
 
 ret

;end of Memory_Copy---------------------------------------------------

 

  最后的最后,附上结果图:

       打算今晚重新整理代码滴,谁料到一个小小的问题害我调试了一整天,原来在LOADER里的平坦的段(数据)没有设置为32位的段,我又在内核里用它来寻堆栈里的地址。。唉。。教训啊。

原创粉丝点击