在这篇我们将利用调用门来调用代码。调用门也有选择子和描述符,注意这里的描述符和前面的有所不同,这里的描述符是由段选择子,段内偏移(前面段选择子所指向的段),属性等等组成。 这里在前面代码的基础上,增加了一个段(通过调用门所调用的段)。在后面的文章中将利用调用门来进行不同特权级间的调用。
下面是代码:
;;nasm 2.07
;;nasm ldt.asm -o ldt.img
org 07c00H
%macro Descriptor 3 ;定义Descriptor结构体宏
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 字节
;定义Gate结构体宏
%macro Gate 4
dw (%2 & 0FFFFh) ; 偏移 1 (2 字节)
dw %1 ; 选择子 (2 字节)
dw (%3 & 1Fh) | ((%4 << 8) & 0FF00h) ; 属性 (2 字节)
dw ((%2 >> 16) & 0FFFFh) ; 偏移 2 (2 字节)
%endmacro ; 共 8 字节
DA_C EQU 98h ; 只读代码段的属性值
DA_DRW EQU 92h ; 允许读写的数据段的属性值
DA_32 EQU 4000h ;32位段的属性值
DA_LDT EQU 82h ; 局部描述符表段类型值
SA_TIL EQU 4 ; 用于指明是LDT的描述符,即选择符中TI=1
DA_386CGate EQU 8Ch ; 386 调用门类型值
DA_DPL0 EQU 00h ; DPL = 0
jmp LABEL_BEGIN
;现在进行第一步:建立GDT表
[SECTION .gdt] ;用于放GDT表的段
;GDT
LABEL_GDT: Descriptor 0,0,0 ;空描述符,当一个任务没有LDT时,会把LDTR清空,这时,LDTR便指向空描述符。
LABEL_CODE32: Descriptor 0,SegCode32Len-1,DA_C + DA_32 ; 代码段的描述符
LABEL_VIDEO: Descriptor 0B8000h,0ffffh,DA_DRW ;显存的描述符,0b8000h是显存的首地址
;GDT 结束
LABEL_NORMAL: Descriptor 0,0ffffh,DA_DRW ;normal描述符,用于将段寄存器的属性规范化(即属性为读写,大小为0ffffh)
LABEL_CODE16: Descriptor 0,0ffffh,DA_C ;16位代码段的描述符
LABEL_GOALCODE: Descriptor 0,GoalCodeLen -1 ,DA_C + DA_32 ;调用门目标段的描述符
LABEL_LDT: Descriptor 0,LdtLen - 1,DA_LDT ;用于描述LDT表的描述符
LABEL_CALL_GATE: Gate SelectorGoalCode,0,0,DA_386CGate + DA_DPL0 ;门描述符
GdtLenth equ $ - LABEL_GDT ;计算GDT表的长度
GdtPtr dw GdtLenth - 1 ; 准备存入GDTR的信息
dd 0 ; 用于存放GDT表的物理地址,后面会计算
;GDT选择子
SelectorLdt equ LABEL_LDT - LABEL_GDT ;ldt表的选择子
SelectorCode32 equ LABEL_CODE32 - LABEL_GDT ;代码段的选择子
SelectorVideo equ LABEL_VIDEO - LABEL_GDT ;显存所在段的选择子
SelectorNormal equ LABEL_NORMAL - LABEL_GDT ;normal描述符的选择子
SelectorCode16 equ LABEL_CODE16 - LABEL_GDT ;16位段的选择子
SelectorGoalCode equ LABEL_GOALCODE - LABEL_GDT ;调用门所调用的目标段的描述符的选择子
SelectorCallGate equ LABEL_CALL_GATE - LABEL_GDT ;调用门的描述符的选择子
;[SECTION .gdt]结束
[SECTION .ldt] ;建立LDT表
;LDT
ALIGN 32
LABEL_TABLE_LDT:
LABEL_CODEA: Descriptor 0,CodeALen - 1 ,DA_C + DA_32 ; ldt表中CODEA段的描述符
LdtLen equ $ - LABEL_TABLE_LDT
SelectorCodeA equ LABEL_CODEA - LABEL_TABLE_LDT + SA_TIL ;这里的SA_TIL表示是LDT中的选择符
;[SECTION .ldt]结束
[SECTION .s16]
[BITS 16] ;目标处理器模式
LABEL_BEGIN:
mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax
mov sp,0100h
mov [LABEL_BACK_TO_REAL + 3],ax ;保存cs的值,用于后面的jmp 0:LABEL_REAL 使它变成 jmp cs:LABEL_REAL
;初始化调用门目标段的描述符
xor eax,eax
mov ax,cs
shl eax,4
add eax,LABEL_SEG_GOALCODE
mov word [LABEL_GOALCODE + 2], ax
shr eax, 16
mov byte [LABEL_GOALCODE + 4], al
mov byte [LABEL_GOALCODE + 7], ah
;初始化LDT在GDT中的描述符
xor eax,eax
mov ax,cs
shl eax,4
add eax,LABEL_TABLE_LDT
mov word [LABEL_LDT + 2], ax
shr eax, 16
mov byte [LABEL_LDT + 4], al
mov byte [LABEL_LDT + 7], ah
;初始化LDT中的描述符
xor eax,eax
mov ax,cs
shl eax,4
add eax,LABEL_SEG_CODEA
mov word [LABEL_CODEA + 2], ax
shr eax, 16
mov byte [LABEL_CODEA + 4], al
mov byte [LABEL_CODEA + 7], ah
; 初始化 16 位代码段描述符
mov ax, cs
movzx eax, ax
shl eax, 4
add eax, LABEL_SEG_CODE16
mov word [LABEL_CODE16 + 2], ax
shr eax, 16
mov byte [LABEL_CODE16 + 4], al
mov byte [LABEL_CODE16 + 7], ah
;初始化32位段的描述符
xor eax,eax ; 同或运算,这里是将eax清零
mov ax,cs
shl eax,4 ;左移四位,相当于乘以16,实模式下计算物理地址
add eax,LABEL_SEG_CODE32 ;eax中为32位段的物理地址
mov word [LABEL_CODE32 + 2],ax ;将32位段的物理地址存入段的描述符中
shr eax,16
mov byte [LABEL_CODE32 + 4],al
mov byte [LABEL_CODE32 + 7],ah
;计算GDT表的物理地址,将GDT表的物理地址信息存入GdtPtr中
xor eax,eax
mov ax,cs
shl eax,4
add eax,LABEL_GDT
mov dword [GdtPtr + 2],eax
;现在进行第二步:将GDT表的信息加载到GDTR中
lgdt [GdtPtr] ;装载GDTR
;关中断
cli
;现在进行第三步:打开地址线
in al,92h
or al,00000010b
out 92h,al
;现在进行第四步:置PE为1,表示CPU处于保护模式下
mov eax,cr0
or eax,1
mov cr0,eax
;现在进入第五步:跳到保护模式的代码
jmp dword SelectorCode32:0 ;这里将SelectorCode32存入CS中作为选择子
;[SECTION .s16] 结束
[SECTION .s32]
[BITS 32]
LABEL_SEG_CODE32:
mov ax,SelectorVideo ;视频选择子,用于找到显存段的描述符
mov gs,ax
mov edi,(80 * 10 + 0)*2 ;屏幕的第10行,第0列
mov ah,0Ch
mov al,'p'
mov [gs:edi],ax ;将字符‘p’输出到屏幕
mov ax,SelectorLdt
lldt ax ;将描述ldt表的描述符的选择子装入LDTR寄存器
call SelectorCallGate:0 ;通过调用门,调用代码段
jmp SelectorCodeA:0 ;调到局部任务
SegCode32Len equ $ - LABEL_SEG_CODE32 ;计算32位代码段的长度
;[SECTION .s32]结束
;ldt表中描述符所指向的CODEA代码段
[SECTION .la]
[BITS 32]
LABEL_SEG_CODEA:
mov ax,SelectorVideo ;视频选择子,用于找到显存段的描述符
mov gs,ax
mov edi,(80 * 11 + 0)*2 ;屏幕的第11行,第0列
mov ah,0Ch
mov al,'l'
mov [gs:edi],ax ;将字符‘l’输出到屏幕
jmp SelectorCode16:0
CodeALen equ $ - LABEL_SEG_CODEA ;计算32位代码段的长度
;[SECTION .la]结束
[SECTION .goalcode]
LABEL_SEG_GOALCODE:
mov ax,SelectorVideo ;视频选择子,用于找到显存段的描述符
mov gs,ax
mov edi,(80 * 12 + 0)*2 ;屏幕的第12行,第0列
mov ah,0Ch
mov al,'c'
mov [gs:edi],ax ;将字符‘l’输出到屏幕
retf
GoalCodeLen equ $ - LABEL_SEG_GOALCODE
;[SECTION .goalcode]结束
[SECTION .code16]
ALIGN 32
[BITS 16]
LABEL_SEG_CODE16:
mov ax,SelectorNormal ;将段寄存器标准化,注意这里jmp指令中的段地址不为0,前面已赋值
mov es,ax
mov ds,ax
mov ss,ax
mov fs,ax
mov gs,ax
mov eax,cr0 ;将PE置零,让CPU处于实模式
and al,11111110b
mov cr0,eax
LABEL_BACK_TO_REAL: ;跳到实模式下
jmp 0:LABEL_REAL
Code16Len equ $ - LABEL_SEG_CODE16
LABEL_REAL: ;进入实模式
;还原es,ss,ds的值
mov ax,cs
mov es,ax
mov ds,ax
mov ss,ax
in al, 92h ;
and al, 11111101b ; 关闭 A20 地址线
out 92h, al ;
sti ;开中断
jmp $
times 510-($-$) db 0
dw 0aa55h