|
发表于 2007-2-5 23:53:38
|
显示全部楼层
那我不自量力说说Grub中实模式和保护模式的切换把。。。= =
其实Grub的实现方法很标准(也许有什么巧妙的地方我没看出来?=v=b)
才疏学浅。。。望各位前辈指教。。。
PS:因为本来是写给学校的同学看的。。。所以有些地方说的比较基础。。。= =
标题:Grub中实现实模式和保护模式切换部分的代码分析
作者:CNLAS
说明:都是些个人愚见。。。出错勿怪。。。还请各位大牛指出。。。= =
/* */中为原文件的注释
/*= =*/中为我加的注释
源文件位于\stage2\asm.S 版本为grub-0.97
提示:对win32asm爱好者一点小提示,grub的asm部分编译是由gcc中的gas来完成的。。。gas采用的asm是延续UNIX的AT&T格式。。。不太了解的同学可以先google学习一下。。。XD
/*
* These next two routines, "real_to_prot" and "prot_to_real" are structured
* in a very specific way. Be very careful when changing them.
*
* NOTE: Use of either one messes up %eax and %ebp.
*/
ENTRY(real_to_prot)
.code16
/*=在Grub这个保护模式过程中。。。只加载了GDT。。。没有设定IDT。。。所以这个保护模式要在关闭可屏蔽中断的情况下运行=*/
cli
/* load the GDT register */
/*=向gdtr寄存器中写入GDT=注1=*/
DATA32 ADDR32 lgdt gdtdesc
/* turn on protected mode */
/*=修改cr0中的pe位切换到保护模式,CR0_PE_ON在shared.h中定义为0x1。。。即将PE位置1=*/
movl %cr0, %eax
orl $CR0_PE_ON, %eax
movl %eax, %cr0
/* jump to relocation, flush prefetch queue, and reload %cs */
DATA32 ljmp $PROT_MODE_CSEG, $protcseg
/*
* The ".code32" directive only works in GAS, the GNU assembler!
* This gets out of "16-bit" mode.
*/
.code32
protcseg:
/* reload other segment registers */
/*=保护模式和实模式中段寄存器的作用是不同的。。。所以这里加载的是段选择子(descriptor)PROT_MODE_DSEG在shared.h中定义为0x10。。。即GDT中保护模式下数据段的选择子=*/
movw $PROT_MODE_DSEG, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
movw %ax, %ss
/* put the return address in a known safe location */
/*=保存返回地址。。。STACKOFF在shared.h中定义为(0x2000 - 0x10)=*/
movl (%esp), %eax
movl %eax, STACKOFF
/* get protected mode stack */
/*=建立保护模式下的栈。。。注2=*/
movl protstack, %eax
movl %eax, %esp
movl %eax, %ebp
/* get return address onto the right stack */
/*=返回地址入栈=*/
movl STACKOFF, %eax
movl %eax, (%esp)
/* zero %eax */
xorl %eax, %eax
/* return on the old (or initialized) stack! */
ret
ENTRY(prot_to_real)
/* just in case, set GDT */
lgdt gdtdesc
/* save the protected mode stack */
/*=保存保护模式下的栈以便下次进入时再次使用=*/
movl %esp, %eax
movl %eax, protstack
/* get the return address */
movl (%esp), %eax
movl %eax, STACKOFF
/* set up new stack */
movl $STACKOFF, %eax
movl %eax, %esp
movl %eax, %ebp
/* set up segment limits */
/*=装入GDT中实模式下数据段的选择子。。。PSEUDO_RM_DSEG是GDT中一个规范段描述符。。。具体意义后面细讲。。。注3=*/
movw $PSEUDO_RM_DSEG, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
movw %ax, %ss
/* this might be an extra step */
ljmp $PSEUDO_RM_CSEG, $tmpcseg /* jump to a 16 bit segment */
tmpcseg:
.code16
/* clear the PE bit of CR0 */
/*=修改cr0的pe位。。。返回实模式。。。CR0_PE_OFF在shared.h中定义为0xfffffffe。。。即将PE位置0=*/
movl %cr0, %eax
andl $CR0_PE_OFF, %eax
movl %eax, %cr0
/* flush prefetch queue, reload %cs */
DATA32 ljmp $0, $realcseg
realcseg:
/* we are in real mode now
* set up the real mode segment registers : DS, SS, ES
*/
/* zero %eax */
/*=这里才是真正将实模式下数据段装入段寄存器=*/
xorl %eax, %eax
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
movw %ax, %ss
/* restore interrupts */
/*=已经回到实模式了。。。置IF为1开启可屏蔽中断。。。中断响应交由中断向量表来控制=*/
sti
/* return on new stack! */
DATA32 ret
.code32
注1:
DATA32 ADDR32 lgdt gdtdesc
gdtdesc是在asm.S最后定义的GDT选择子。。。由lgdt命令读入。。。该命令将48位存储器操作数读入gdtr寄存器。。。高双字为段基地址。。。低字是以字节为单位的段界限
/* this is the GDT descriptor */
gdtdesc:
.word 0x27 /* limit */ /*=定义了5个表项。。。每项8位。。共40位。。。=*/
.long gdt /* addr */
gdt的定义就在gdtdesc的上方。。。而且源代码中还很友好的给出了GDT表项的结构图
/*
* This is the Global Descriptor Table
*
* An entry, a "Segment Descriptor", looks like this:
*
* 31 24 19 16 7 0
* ------------------------------------------------------------
* | | |B| |A| | | |1|0|E|W|A| |
* | BASE 31..24 |G|/|0|V| LIMIT |P|DPL| TYPE | BASE 23:16 |
* | | |D| |L| 19..16| | |1|1|C|R|A| |
* ------------------------------------------------------------
* | | |
* | BASE 15..0 | LIMIT 15..0 |
* | | |
* ------------------------------------------------------------
*
* Note the ordering of the data items is reversed from the above
* description.
*/
.p2align 2 /* force 4-byte alignment */
gdt:
/*=GDT的0项。。。必须为空=*/
.word 0, 0
.byte 0, 0, 0, 0
/*=保护模式下代码段的描述符
由表项定义它是一个可执行可读非一致的有效的32位代码段。。。
基地址是00000000H。。。以4K字节为单位的段界限值是0FFFFFH
描述符特权级DPL=0
=*/
/* code segment */
.word 0xFFFF, 0
.byte 0, 0x9A, 0xCF, 0
/*=保护模式下数据段的描述符
由表项定义它是一个可读可写有效的32位数据段。。。
基地址是00000000H。。。以4K字节为单位的段界限值是0FFFFFH
描述符特权级DPL=0
=*/
/* data segment */
.word 0xFFFF, 0
.byte 0, 0x92, 0xCF, 0
/*=实模式下代码段的描述符
由表项定义它是一个可执行可读一致有效的16位代码段。。。
基地址是00000000H。。。以字节为单位的段界限值是0FFFFH
描述符特权级DPL=0
=*/
/* 16 bit real mode CS */
.word 0xFFFF, 0
.byte 0, 0x9E, 0, 0
/*=实模式下数据段的描述符
由表项定义它是一个可读可写有效的16位数据段。。。
基地址是00000000H。。。以字节为单位的段界限值是0FFFFH
描述符特权级DPL=0
=*/
/* 16 bit real mode DS */
.word 0xFFFF, 0
.byte 0, 0x92, 0, 0
注2:
movl protstack, %eax
1)protstact是在asm.S中定义个一个长整型。。。初始化值为PROTSTACKINIT
protstack:
.long PROTSTACKINIT
2)PROTSTACKINIT在shared.h中定义为(FSYS_BUF - 0x10)
#define PROTSTACKINIT (FSYS_BUF - 0x10)
3)FSYS_BUF在shared.h中定义为
#define FSYS_BUF RAW_ADDR (0x68000)
4)RAW_ADDR在shared.h的最上面是这么定义的。。。
/* Maybe redirect memory requests through grub_scratch_mem. */
#ifdef GRUB_UTIL
extern char *grub_scratch_mem;
# define RAW_ADDR(x) ((x) + (int) grub_scratch_mem)
# define RAW_SEG(x) (RAW_ADDR ((x) << 4) >> 4)
#else
# define RAW_ADDR(x) (x)
# define RAW_SEG(x) (x)
#endif
注3:
为何要在切换会实模式之前把一个规范化描述符选择子装入那些段寄存器呢?
这涉及到80x86架构的CPU内部结构问题。。。
从80286开始每个段寄存器都配有一个高速缓冲寄存器。。。。这些寄存器对程序员是不可见的。。。。当给段寄存器装入选择子的时候,处理器自动从描述符表中将对应的描述符信息装入与段寄存器对应的高速缓冲寄存器中。。。以后访问该段时只要从这里读信息就行了。。。不必再去描述符表中查询。。。在保护模式下。。。可以通过将相应的选择子装入段寄存器来刷新这些高速缓冲器
在实模式中这些高速缓冲寄存器仍然发挥作用。。。实模式中段基址是段值*16。。。每个段的32位段界限都是固定的0FFFFH。。。而且很多属性都是固定的。。。实模式中不存在GDT没有办法设置这些高速缓冲寄存器中的属性。。。只能沿用保护模式下的值。。。所以在从保护模式返回实模式之前。。。一定要加载一个合适的描述符选择子到相关的段寄存器。。。从而使得这些高速缓冲寄存器满足实模式的要求
实力所限。。。只能分析到这种地步了。。。望达人修正补充。。。多谢~~~ |
|