寻址方式不同是直接区别,更深层次区别是80386引入了权限检查,实现了保护模式。
之前的认识误区:386引入段机制,是出于兼容8086的段机制,现在理解386并没有在寻址上兼容8086(如果兼容两者寻址算法应该一样)。所谓“兼容”是寄存器上的沿用,使8086引入的段寄存器不被抛弃,除了把段寄存器作为仍然作寻址用,还利用多出来低3位进行权限检查。使保护模式成为可能。
org 07c00h ; 告诉编译器程序加载到7c00处 jmp 07c0h:DispStrOff code: times 10 db 0 ; never reach here DispStrOff equ $ - $$ DispStr: mov ax, BootMessage mov bp, ax ; ES:BP = 串地址 mov cx, 16 ; CX = 串长度 mov ax, 01301h ; AH = 13, AL = 01h mov bx, 000ch ; 页号为0(BH = 0) 黑底红字(BL = 0Ch,高亮) mov dl, 0 int 10h ; 10h 号中断 jmp $ BootMessage: db "Hello, OS world!" [email protected]:/home/work/orange/source/mywork# cat real_mode.dis 00007C00 EA0F00C007 jmp 0x7c0:0xf //使用段寄存器的跳转 cs:0x7c0 off=0xf 00007C05 0000 add [bx+si],al 00007C07 0000 add [bx+si],al 00007C09 0000 add [bx+si],al 00007C0B 0000 add [bx+si],al 00007C0D 0000 add [bx+si],al 00007C0F B8237C mov ax,0x7c23 00007C12 89C5 mov bp,ax 00007C14 B91000 mov cx,0x10 00007C17 B80113 mov ax,0x1301 00007C1A BB0C00 mov bx,0xc 00007C1D B200 mov dl,0x0 00007C1F CD10 int 0x10 00007C21 EBFE jmp short 0x7c21 // 没有使用段寄存器的短跳转,直接使用偏移。不使用CS参与计算。 ......
上面一段程序直接写死了cs的值,依然可以成功跳转,因为我们知道cpu计算地址的方法,反汇编代码后得到跳转的目标地址0x7C0F,反推出CS的值。
JMP前,CPU准备执行下面这条指令,此时cs = 0,ip = 0x7C00。当前地址 = 0 * 16 + 0x7C00 = 0x7C00
CS没有内容。
00007C00 EA0F00C007 jmp 0x7c0:0xf
运行结果:
JMP执行后:当前地址 = 0x7C0 * 16 + 0xF = 0x7C0F,可以看到执行时CS寄存器被加载,IP寄存器被加载。
一句话解释,cpu利用段寄存器查找描述符获取物理地址基址,加上偏移算出物理地址
描述符表定义所有段的物理地址基址,段寄存器提供在表中查找描述符的索引
物理地址 = GDT[index]取段基址部分 + OFF或者物理地址 = LDT[index]取段基址部分 + OFF
保护模式寻址流程
实验
%include "pm.inc" ; 常量, 宏, 以及一些说明 org 07c00h jmp LABEL_BEGIN ;1)准备代码段,计算出段基址和段长度 [SECTION .s32]; 32 位代码段. 由实模式跳入. [BITS 32] LABEL_SEG_CODE32: // 标号就是段基址 mov ax, SelectorVideo mov gs, ax ; 视频段选择子(目的) mov edi, (80 * 11 + 79) * 2 ; 屏幕第 11 行, 第 79 列。 mov ah, 0Ch ; 0000: 黑底 1100: 红字 mov al, 'P' mov [gs:edi], ax ; 到此停止 jmp $ SegCode32Len equ $ - LABEL_SEG_CODE32 // 计算段长度 ; END of [SECTION .s32] ;2)定义GDT(Global Descriptor Table),先定义一个格式,段基址后面写入 [SECTION .gdt] ; GDT ; 段基址, 段界限 , 属性 LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符 LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32; 非一致代码段 LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 显存首地址 ; GDT 结束 GdtLen equ $ - LABEL_GDT ; GDT长度 GdtPtr dw GdtLen - 1 ; GDT界限 dd 0 ; GDT基地址 ;3)定义选择子 ; GDT 选择子 SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT ; END of [SECTION .gdt] ;4)填入之前没有写入的段基址 [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 ax, cs shl eax, 4 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 ;5)计算GDT地址,加载 GDTR xor eax, eax mov ax, ds shl eax, 4 add eax, LABEL_GDT ; eax <- gdt 基地址 mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址 ; 加载 GDTR lgdt [GdtPtr] ;6)关中断,打开地址线,cr0 PE 位置位 ; 关中断 cli ; 打开地址线A20 in al, 92h or al, 00000010b out 92h, al ; 准备切换到保护模式 mov eax, cr0 or eax, 1 mov cr0, eax ;7)进入保护模式 jmp dword SelectorCode32:0 ; 执行这一句会把 SelectorCode32 装入 cs, ; END of [SECTION .s16]
运行结果:
我们在jmp dword SelectorCode32:0
前断住,查看当前指令地址0x000000007c8d
,段选择子0x0008
,cs内容为0。
选择子低三位做其它用,其余部分作为索引,index=8%8 = 1,指示cpu去gdt表的第1条去描述符,查看gdt表第一条基址为base=0x00007c04
,得到目标跳转地址0x00007c04+0=0x00007c04
。
附:段选择子介绍
上图是intel手册介绍段选择子的图片。 TI:Table Indicator,指示cpu去GDT(TI=0)取描述符,还是去LDT(TI=1)中取描述符。LDT表属于系统段,在GDT表之后加载。 RPL:Request Privilege Level,指示CPU申请权限。