直接上代码,以及追加了自己对GDT的理解,为了理解GDT,翻遍了各种文章,但没有代码的支撑,凭空的理解很浪费时间。
下面的代码,稍微简化了原先的代码(于老师的代码^^)。把pm.asm需要的代码从pm.inc挪出来,取消了pm.inc的引入。
; ==========================================
; 32mode_TEXT.asm
; 编译方法:nasm 32mode_TEXT.asm -o 32mode_TEXT.bin
; ==========================================
;=========================宏定义: GDT的定义 开始=====================================
;
; 描述符 类型 入参1(%1), 入参2(%2), 入参3(%3)
; usage: Descriptor Base, Limit, Attr
; Base: dd
; Limit: dd (low 20 bits available)
; Attr: dw (lower 4 bits of higher byte are always 0)
%macro Descriptor 3
dw %2 & 0FFFFh ; 段界限1(双字节:字节1~字节2)
dw %1 & 0FFFFh ; 段基址1(双字节:字节3~字节4)
db (%1 >> 16) & 0FFh ; 段基址2(单字节:字节5)
dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性1 + 段界限2 + 属性2(双字节:字节6~字节7)
db (%1 >> 24) & 0FFh ; 段基址3(单字节:字节8)
%endmacro ; 共 8 字节
;
; 门 类型 入参1, 入参2, 入参3, 入参4
; usage: Gate Selector, Offset, DCount, Attr
; Selector: dw
; Offset: dd
; DCount: db
; Attr: db
%macro Gate 4 ; 4为参数个数
dw (%2 & 0FFFFh) ; 偏移1 (双字节)
dw %1 ; 选择子(双字节)
dw (%3 & 1Fh) | ((%4 << 8) & 0FF00h) ; 属性 (双字节)
dw ((%2 >> 16) & 0FFFFh) ; 偏移2 (双字节)
%endmacro ; 共 8 字节
;=========================GDT的定义 结束=====================================
org 07c00h
jmp LABEL_BEGIN
[SECTION .gdt]
; GDT
; Descriptor在pm.inc中定义,三个入参
; LABEL_GDT为(0#)描述符,创建0#描述符,它是空描述符,这是处理器的要求
; 段基址, 段界限 , 属性
LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, 98h + 4000h; 非一致代码段
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, 92h ; Text显存首地址:0B8000h,VGA显存首地址:0x000a0000
; GDT 结束
GdtLen equ $ - LABEL_GDT ; GDT长度
GdtPtr dw GdtLen - 1 ; GDT界限
dd 0 ; GDT基地址
; GDT 选择子
; LABEL_GDT的地址为GDT表起始地址
; 后面的GDT的地址,都是以LABEL_GDT的地址为起始地址,计算自己的地址
SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
; END of [SECTION .gdt]
[SECTION .s16]
[BITS 16]
LABEL_BEGIN:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0100h
; 初始化 32 位代码段描述符
; xor eax,eax和mov eax,0一样,由于它比mov eax,0效率高,所以一般用它!
; 两者的作用没有区别,都是让eax的值为0,但是xor eax,eax 指令为2字节,mov eax,0 指令为5个字节。相比而言,前面指令更能节省空间。
; 这个虽然结果是一样的,都是变成0,但是xor会影响到状态标志位,使cf of 变成0。
xor eax, eax
mov ax, cs
shl eax, 4 ; shl:逻辑左移指令 shr:逻辑右移指令
add eax, LABEL_SEG_CODE32
mov word [LABEL_DESC_CODE32 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_CODE32 + 4], al
mov byte [LABEL_DESC_CODE32 + 7], ah
; 为加载 GDTR 作准备
xor eax, eax; “XOR EAX,EAX”语句已经将EAX清零了。
mov ax, ds ; 然后“MOV AX,DS”将16位的段值从DS复制到AX。
shl eax, 4 ; “SHL EAX,4”是计算数据段的起始物理地址,
; 所以用了EAX来表示20位的物理地址(没有使用AX表示物理地址是因为AX只有16位)。
; (注:实模式下的寻址的方式固然还是段:偏移,而且段:偏移还都是16位,
; 但在实模式下却可以使用32位寄存器,如EAX……等,
; 而这里的EAX既不是段值,也不是偏移,而是人工计算出的物理地址,
; 相当于用EAX保存的数据,
; 最后再通过“MOV DWORD[GdtPtr+2],EAX”将EAX中的物理地址送到DS:(OFFSET GdtPtr)+2表示的逻辑地址处。)
add eax, LABEL_GDT ; eax <- gdt 基地址
mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址
; 加载 GDTR
lgdt [GdtPtr]
; 关中断
cli
; 打开地址线A20
; 打开A20的其他方法:https://blog.csdn.net/longintchar/article/details/79365928
; 下面写法是其中之一
in al, 92h ;南桥芯片内的端口
or al, 00000010b
out 92h, al
; 准备切换到保护模式--除了cr0还有其他控制寄存器(CR0,CR1,CR2,CR3,CR4)
mov eax, cr0; cr0是80386CPU中的控制寄存器
or eax, 1 ; 第0位,即PE位,用来控制是否进入保护模式(是/否:1/0)
mov cr0, eax; 设置PE位
; 真正进入保护模式
jmp dword SelectorCode32:0 ; 执行这一句会把 SelectorCode32 装入 cs,
; 并跳转到 SelectorCode32:0 处
; 【jmp dword SelectorCode32:0】的理解
; http://tieba.baidu.com/p/4302738682
; SelectorCode32:0的意思就是第二个描述符的第0行代码开始,这里的0是不是可以换成32位模式下的任何函数?
; END of [SECTION .s16]
[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS 32]
LABEL_SEG_CODE32:
mov ax, SelectorVideo
mov gs, ax ; 视频段选择子(目的)
mov ecx, 28 ; .show的循环次数
mov edi, 0
mov bx, BootMessage
.show:
mov ah, 0Ch ; 0000: 黑底 1100: 红字
mov al, [bx]
mov [gs:edi], ax
inc edi
inc edi
inc bx
loop .show
; 到此停止
jmp $
BootMessage: db "Hello CPU, I'm in protected mode!"
SegCode32Len equ $ - LABEL_SEG_CODE32
; END of [SECTION .s32]
用GDB查看GDT的内容(64位GCC在Windows下模拟)
GDT和段的关系
代码:
mov ax, SelectorVideo ; SelectorVideo 为LABEL_DESC_CODE32的选择子
mov gs, ax ; gs指向GDT(LABEL_DESC_CODE32)的段基址
这时gs指向0x000b8000, 即段描述符的段基址。界限值:0xffff
剩下就和正常的段寄存器操作一样了。
gs:edi等