一看二做三总结
分类: 嵌入式
2011-01-16 00:57:02
GDT表是存储GDT描述符的数组,每一个描述符对应着一个段。GDT表中的第一项必须为全0。表项类型为结构体Descriptor,结构如下:
; usage: Descriptor Base, Limit, Attr ; %1 Base: dd ; %2 Limit: dd (low 20 bits available) ; %3 Attr: dw (lower 4 bits of higher byte are always 0) %macro Descriptor 3 dw %2 & 0FFFFh ; 段界限 1 (2 字节) dw %1 & 0FFFFh ; 段基址 1 (2 字节) db (%1 >> 16) & 0FFh ; 段基址 2 (1 字节) dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性 1 + 段界限 2 + 属性 2 (2 字节) db (%1 >> 24) & 0FFh ; 段基址 3 (1 字节) %endmacro ; 共 8 字节 |
根据nasm编译器的规则,结构体定义以%macro开始,以%endmacro结束。结构体名为Descriptor,宏名后的3表示该结构体有三个成员变量。
GDT表的定义方式如下:
[SECTION .gdt] ; GDT Base Addr Limit Attrib LABEL_DES_GDT: Descriptor 0, 0, 0 LABEL_DES_CODE32: Descriptor 0, Code32Len - 1, DA_C + DA_32 LABEL_DES_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; GDT end |
这里可以看到给结构体赋值的方式,每一项对应结构体中的一个成员变量。以表项LABEL_DES_VIDEO为例,0B8000h对应%1,0ffffh对应%2,DA_DRW对应%3。
注意:表项LABEL_DES_CODE32保存的是我们要跳入的32位代码段的GDT表项,其中Base Addr是0,因为此时还不知道32为代码段的地址,在后面的16为代码段中才会设置它。
GdtLen equ $ - LABEL_DES_GDT ; Gdt Len GdtPtr dw GdtLen - 1 ; Gdt Limit dd 0 ; Gdt Base Addr |
GDT表的地址保存在寄存器Gdtr中,该地址首先通过上面的语句计算而得后存在变量GdtPtr中,之后才加载。GdtPtr低2字节保存GDT表长度,通过“$ - LABEL_DES_GDT”计算而得。GdtPtr高4字节保存GDT表基址,这里先置0,后面会存入地址。
; Gdt Selector SelectorCode32 equ LABEL_DES_CODE32 - LABEL_DES_GDT SelectorVideo equ LABEL_DES_VIDEO - LABEL_DES_GDT
|
选择子Selector中保存的是各段对应GDT表项相对地址,通过上面的语句计算而得。
[SECTION .s16] [BITS 16] …… [SECTION .s32] [BITS 32] …… |
由于本程序实现了有实模式到保护模式的跳转,所以需要在程序中定义两个代码段,一个是16位段;一个是32位段。
; Init 32 bits code segment descriptor xor eax, eax mov ax, cs shl eax, 4 add eax, LABEL_CODE32 mov word [LABEL_DES_CODE32 + 2], ax shr eax, 16 mov byte [LABEL_DES_CODE32 + 4], al mov byte [LABEL_DES_CODE32 + 7], ah
|
首先要设置32为代码段对应GDT表项的基址。基址对应着表项中的第2、3、4、7,四个字节。
; Prepare for loading gdtr xor eax, eax mov ax, cs shl eax, 4 add eax, LABEL_DES_GDT mov dword [GdtPtr + 2], eax |
这段代码用于设置变量GdtPtr,长度为2字节。
; Load gdtr lgdt [GdtPtr] |
此处加载gdtr寄存器,x86使用专门的指令lgdt实现gdtr寄存器的设置。
; close int Cli |
由于实模式与保护模式下的中断模式不同,所以在实模式/保护模式转换时必须关闭中断。
; open A20 in al, 92h or al, 00000010b out 92h, al |
80286及之前的cpu最大寻址空间为1M,超过1M的地址(也就是使用了A20之后的地址线),会开始从0重新计算,即使跳入保护空间,也无法使用超过1M的地址。
80386开始,增加了保护模式,寻址空间变为4G。为了与80286之前的cpu兼容,超过1M的地址平时是关闭的,只有打开A20后才能访问超过1M的地址。
上面只是打开A20的一种方式,但不是唯一的方式。
; open protected mode mov eax, cr0 or eax, 1 mov cr0, eax |
此处打开cr0的PE位,实际上就使能了保护模式,也就是说,“mov cr0, eax”这一句后,系统就运行于保护模式下了。但是,此时ics的值仍然是实模式下的值,我们需要把代码段的Selector选择子装入cs。
; jump into protected mode jmp dword SelectorCode32:0
|
这个跳转是为了设置cs寄存器,其目的是选择子SelectorCode32对应的GDT描述符LABEL_DESC_CODE32对应的段首地址,即标号LABEL_ CODE32C处。
该句指令还在16位段中执行,而目标地址却是32位的,从这一点上看,他是混合16位于32位的代码。所以,jmp后的dword就必不可少了。如果没有dword,编译出来的只是16位代码。假设目标地址偏移量为0x12345678,执行jmp SelectorCode32:0x12345678,则等价于jmp SelectorCode32:0x5678。
Nasm编译器的好处之一就是可以通过jmp后添加dword,指定编译后为32位指令。
至此,我们真正跳入了保护模式。
LABEL_CODE32: mov eax, SelectorVideo mov gs, eax mov edi, (80 * 6 + 6) * 2 ; Line: 6, Column: 6 mov ah, 0Ch ; 0000: background(black) 1100: foreground(red) mov al, 'P' mov [gs:edi], ax |
这是进入保护模式后执行的代码。SelectorVideo选择子对应着显存的基址,我们通过把‘P’放入显存,实现了字符‘P’的显示。