重新认识intel段机制寻址方式

段机制在8086和80386的区别

寻址方式不同是直接区别,更深层次区别是80386引入了权限检查,实现了保护模式。
之前的认识误区:386引入段机制,是出于兼容8086的段机制,现在理解386并没有在寻址上兼容8086(如果兼容两者寻址算法应该一样)。所谓“兼容”是寄存器上的沿用,使8086引入的段寄存器不被抛弃,除了把段寄存器作为仍然作寻址用,还利用多出来低3位进行权限检查。使保护模式成为可能。

8086段寄存器寻址方式

  • 一句话解释,cpu利用段寄器左移4bit取值,加上偏移地址算出物理地址
    物理地址=段寄存器*16 + 偏移
  • 实验
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寄存器被加载。
在这里插入图片描述

80386段寄存器寻址方式(分页未开启情况)

  • 一句话解释,cpu利用段寄存器查找描述符获取物理地址基址,加上偏移算出物理地址
    描述符表定义所有段的物理地址基址,段寄存器提供在表中查找描述符的索引

    物理地址 = GDT[index]取段基址部分 + OFF或者物理地址 = LDT[index]取段基址部分 + OFF

  • 保护模式寻址流程

    • 编写代码段,数据段
    • 计算出段基址和段长度
    • 定义GDT表,填入段基址
    • 定义选择子,方便寻址时载入段寄存器
    • 计算GDT地址,使用lgdt指令加载到GDTR
    • 关中断,打开A20地址线,cr0最低位PE置位
    • jmp指令加载选择子到CS,进入保护模式
  • 实验

    • 准备阶段
%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申请权限。