首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >实战局部描述符表 LDT

实战局部描述符表 LDT

作者头像
用户3147702
发布于 2022-06-27 14:21:07
发布于 2022-06-27 14:21:07
1.4K0
举报

1. 引言

经过多篇文章的介绍,我们对 32 位保护模式已经有了很深的认识,尤其是分段机制以及由此带来的对内存的保护。 进军保护模式 保护模式进阶 — 再回实模式

这一切都和 32 位保护模式全新的内存分段和寻址方式密不可分。 经过上面两篇文章的介绍,全局描述符表 GDT 的结构和使用已经非常清晰了,本文我们来介绍与他非常类似的局部描述符表 — LDT。

2. 局部描述符表 LDT

此前,我们已经介绍过局部描述符表。

IA-32 CPU 可以寻址 4GB 大小的内存,只用一个 GDT 来进行分段显得粒度过大,LDT 则提供了进一步细分内存段的机制。 内存中只能有一个 GDT,但却可以存在多个 LDT,如上图所示,每个 LDT 作为 GDT 中一个描述符描述的内存段。 通常,一个 LDT 用于划分一个特定任务执行过程中需要使用的内存分段。

3. 描述符结构

LDT 描述符与 GDT 描述符的结构是一模一样的:

可以参看: 详解 32 位保护模式与内存分段机制

4. 实战 LDT

那么,接下来我们就来实际写代码操作,使用 LDT 完成我们的功能。

4.1. 创建代码段,编写代码

我们将原本在 GDT 中的显示代码独立到一个新的 SECTION 中。

代码语言:javascript
复制
[SECTION .ldtcode]
ALIGN 32
[BITS 32]
LABEL_CODE_A:
    xor edi, edi
    mov    edi, 80 * 2 * 2         ; 屏幕第 2 行, 第 0 列
    xor esi, esi
    mov esi, OffsetBootMessage
    call DisplayString

    jmp SelectorCode16:0        ; 跳回实地址模式

DisplayString:
    push eax
    mov    ah, 8Ch                    ; 0000: 黑底    1100: 红字
    cld
.loop_label:
    lodsb
    test al, al
    jz .over_print
    mov    [gs:edi], ax
    add    edi, 2
    jmp .loop_label

.over_print:
    pop eax
    ret
CodeALen    equ    $ - LABEL_CODE_A

4.2. 创建 LDT

和创建 GDT 一样,我们首先需要在内存上开辟一个空间来存储局部描述符表,并加入指向上述代码的描述符及选择子。

代码语言:javascript
复制
; LDT
[SECTION .ldt]
ALIGN   32
LABEL_LDT:
;                            段基址       段界限      属性
LABEL_LDT_DESC_CODEA: Descriptor 0, CodeALen - 1,    4098h ; Code, 32 位
LDTLen equ $ - LABEL_LDT

SelectorLDTCodeA    equ LABEL_LDT_DESC_CODEA    - LABEL_LDT + 4 ; 置位 TI 位,表示是 LDT

4.3. 在 GDT 中创建指向 LDT 的描述符及选择子

代码语言:javascript
复制
LABEL_DESC_LDT:    Descriptor       0,       LDTLen - 1, 82h        ; LDT

SelectorLDT         equ LABEL_DESC_LDT      - LABEL_GDT

4.4. 初始化 GDT 中指向 LDT 的描述符的段基址及 LDT 代码描述符段基址

代码语言:javascript
复制
; 初始化 LDT 在 GDT 中的描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_LDT
mov word [LABEL_DESC_LDT + 2], ax
shr eax, 16
mov byte [LABEL_DESC_LDT + 4], al
mov byte [LABEL_DESC_LDT + 7], ah

; 初始化 LDT 中的描述符
xor    eax, eax
mov    ax, ds
shl    eax, 4
add    eax, LABEL_CODE_A
mov    word [LABEL_LDT_DESC_CODEA + 2], ax
shr    eax, 16
mov    byte [LABEL_LDT_DESC_CODEA + 4], al
mov    byte [LABEL_LDT_DESC_CODEA + 7], ah

4.5. 修改实地址模式进入保护模式初始代码,跳转到 LDT

汇编指令 lgdt 用来将 GDT 首地址加载 GDTR,与此对应,lldt 则用来将 LDT 首地址加载到 LDTR 寄存器,因此需要在跳转到具体的 LDT 代码段前执行:

代码语言:javascript
复制
; Load LDT 跳入局部任务
mov ax, SelectorLDT
lldt    ax
jmp SelectorLDTCodeA : 0

5. 执行结果

经过上述一系列操作,最终,我们可以通过虚拟机加载执行,看到如下执行效果:

6. 后记

在例如 80286 等没有分页功能的处理器上,LDT 提供了多个进程实现独立地址空间的功能,每个用户进程分配一个单独的 LDT 来描述私有内存,实现每个进程单独的内存特权级等属性的定义,同时,GDT 描述共享内存和内核内存。 由于 GDT 描述的内存对于所有进程来说都是可见的,且具有相同权限,如果需要为每个进程单独定义权限,也可以通过定义 LDT 的方式来解决,具体做法是创建 LDT 描述符,定义独立的属性,但内存指向 GDT 描述符所描述的相同内存。 但是,通过 LDT 来解决进程间内存独立的问题,其代价是寄存器的反复加载,这对于 CPU 来说是一件较为耗时的操作,于是,80386 开始,Intel 引入了内存分页功能,相比于 LDT,更为灵活高效,因此 LDT 已经基本不会被使用了。

7. 附录 — 完整代码

代码语言:javascript
复制
; ---------------- 内存段描述符宏 -------------
; 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
    dw    %1 & 0FFFFh                            ; 段基址1
    db    (%1 >> 16) & 0FFh                    ; 段基址2
    dw    ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)    ; 属性1 + 段界限2 + 属性2
    db    (%1 >> 24) & 0FFh                    ; 段基址3
%endmacro

; ------------ DOS 加载初始内存地址 -----------
org    0100h
    jmp    LABEL_BEGIN

; ------------------- GDT ---------------------
[SECTION .gdt]
; GDT
;                              段基址,           段界限,        属性
LABEL_GDT:           Descriptor       0,                0, 0            ; 空描述符
LABEL_DESC_NORMAL: Descriptor       0,           0ffffh, 92h        ; Normal 描述符
LABEL_DESC_CODE32: Descriptor        0, SegCode32Len - 1, 4098h      ; 非一致代码段
LABEL_DESC_CODE16: Descriptor        0,           0ffffh, 98h          ; 非一致代码段, 用于跳回 16 BITS 模式
LABEL_DESC_DATA:   Descriptor        0,          DataLen-1, 92h           ; 可读写数据段,界限 64KB
LABEL_DESC_STACK:  Descriptor       0,       TopOfStack, 4093h      ; 32 位全局堆栈段,可读写数据段,且栈指针默认使用 esp 寄存器
LABEL_DESC_VIDEO:  Descriptor 0B8000h,           0ffffh, 92h        ; 显存首地址
LABEL_DESC_LDT:    Descriptor       0,       LDTLen - 1, 82h        ; LDT
; ------------------ END OF GDT ----------------

GdtLen        equ    $ - LABEL_GDT    ; GDT长度
GdtPtr        dw    GdtLen - 1        ; GDT界限
            dd    0                ; GDT基地址

; ------------------ GDT 选择子 -----------------
SelectorNormal        equ    LABEL_DESC_NORMAL    - LABEL_GDT
SelectorCode32        equ    LABEL_DESC_CODE32    - LABEL_GDT
SelectorCode16        equ    LABEL_DESC_CODE16    - LABEL_GDT
SelectorData        equ    LABEL_DESC_DATA        - LABEL_GDT
SelectorStack        equ    LABEL_DESC_STACK    - LABEL_GDT
SelectorVideo        equ    LABEL_DESC_VIDEO    - LABEL_GDT
SelectorLDT         equ LABEL_DESC_LDT      - LABEL_GDT
; --------------- END OF 段选择子 ----------------

[SECTION .data1]     ; 数据段
ALIGN    32
[BITS    32]
LABEL_DATA:
SPValueInRealMode    dw    0
BootMessage:        db  "Hello World my OS!", 0
OffsetBootMessage    equ    BootMessage - $$
DataLen                equ    $ - LABEL_DATA

; 全局堆栈段
[SECTION .gs]
ALIGN    32
[BITS    32]
LABEL_STACK:
    times 512 db 0
TopOfStack    equ    $ - LABEL_STACK - 1

[SECTION .s16]
[BITS    16]
LABEL_BEGIN:
    ; 初始化段基址寄存器
    mov    ax, cs
    mov    ds, ax
    mov    es, ax
    mov    ss, ax
    mov    sp, 0100h

    mov    [LABEL_GO_BACK_TO_REAL+3], ax
    mov    [SPValueInRealMode], sp

    ; 初始化 16 位代码段描述符
    mov    ax, cs
    movzx    eax, ax
    shl    eax, 4
    add    eax, LABEL_SEG_CODE16
    mov    word [LABEL_DESC_CODE16 + 2], ax
    shr    eax, 16
    mov    byte [LABEL_DESC_CODE16 + 4], al
    mov    byte [LABEL_DESC_CODE16 + 7], ah

    ; 初始化非一致代码段描述符
    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

    ; 初始化数据段描述符
    xor    eax, eax
    mov    ax, ds
    shl    eax, 4
    add    eax, LABEL_DATA
    mov    word [LABEL_DESC_DATA + 2], ax
    shr    eax, 16
    mov    byte [LABEL_DESC_DATA + 4], al
    mov    byte [LABEL_DESC_DATA + 7], ah

    ; 初始化堆栈段描述符
    xor    eax, eax
    mov    ax, ds
    shl    eax, 4
    add    eax, LABEL_STACK
    mov    word [LABEL_DESC_STACK + 2], ax
    shr    eax, 16
    mov    byte [LABEL_DESC_STACK + 4], al
    mov    byte [LABEL_DESC_STACK + 7], ah

    ; 初始化 LDT 在 GDT 中的描述符
    xor eax, eax
    mov ax, ds
    shl eax, 4
    add eax, LABEL_LDT
    mov word [LABEL_DESC_LDT + 2], ax
    shr eax, 16
    mov byte [LABEL_DESC_LDT + 4], al
    mov byte [LABEL_DESC_LDT + 7], ah

    ; 初始化 LDT 中的描述符
    xor    eax, eax
    mov    ax, ds
    shl    eax, 4
    add    eax, LABEL_CODE_A
    mov    word [LABEL_LDT_DESC_CODEA + 2], ax
    shr    eax, 16
    mov    byte [LABEL_LDT_DESC_CODEA + 4], al
    mov    byte [LABEL_LDT_DESC_CODEA + 7], ah

    ; 准备加载 GDTR
    xor    eax, eax                ; 清空 eax 寄存器
    mov    ax, ds
    shl    eax, 4
    add    eax, LABEL_GDT            ; 计算出 GDT 基地址的物理地址
    mov    dword [GdtPtr + 2], eax    ; [GdtPtr + 2] <- gdt 基地址

    ; 加载 GDTR
    lgdt    [GdtPtr]

    ; 关闭硬件中断
    cli

    ; 打开 A20 地址总线
    in    al, 92h
    or    al, 00000010b
    out    92h, al

    ; 置位 PE 标志位,打开保护模式
    mov    eax, cr0
    or    eax, 1
    mov    cr0, eax

    ; 跳转进入保护模式
    jmp    dword SelectorCode32:0    ; 执行这一句会把 SelectorCode32 装入 cs,
                                ; 并跳转到 Code32Selector:0  处

LABEL_REAL_ENTRY:        ; 从保护模式跳回到实模式就到了这里
    mov    ax, cs
    mov    ds, ax
    mov    es, ax
    mov    ss, ax

    mov    sp, [SPValueInRealMode]

    ; 关闭 A20 地址线
    in    al, 92h
    and    al, 0fdh
    out    92h, al

    ; 打开硬件中断
    sti

    ; 触发 BIOS int 21h 中断,回到实地址模式
    mov    ax, 4c00h
    int    21h

[SECTION .s32]    ; 32 位代码段. 由实模式跳入.
[BITS    32]
LABEL_SEG_CODE32:
    mov    ax, SelectorData
    mov    ds, ax                    ; 数据段选择子

    mov    ax, SelectorVideo
    mov    gs, ax                    ; 赋值视频段选择子

    mov    ax, SelectorStack
    mov    ss, ax                    ; 堆栈段选择子

    mov    esp, TopOfStack

    ; Load LDT 跳入局部任务
    mov ax, SelectorLDT
    lldt    ax
    jmp SelectorLDTCodeA : 0

SegCode32Len        equ    $ - LABEL_SEG_CODE32

; LDT
[SECTION .ldt]
ALIGN   32
LABEL_LDT:
;                            段基址       段界限      属性
LABEL_LDT_DESC_CODEA: Descriptor 0, CodeALen - 1,    4098h ; Code, 32 位
LDTLen equ $ - LABEL_LDT

; LDT 选择子
SelectorLDTCodeA    equ LABEL_LDT_DESC_CODEA    - LABEL_LDT + 4 ; 置位 TI 位,表示是 LDT

[SECTION .ldtcode]
ALIGN 32
[BITS 32]
LABEL_CODE_A:
    xor edi, edi
    mov    edi, 80 * 2 * 2         ; 屏幕第 2 行, 第 0 列
    xor esi, esi
    mov esi, OffsetBootMessage
    call DisplayString

    jmp SelectorCode16:0        ; 跳回实地址模式

DisplayString:
    push eax
    mov    ah, 8Ch                    ; 0000: 黑底    1100: 红字
    cld
.loop_label:
    lodsb
    test al, al
    jz .over_print
    mov    [gs:edi], ax
    add    edi, 2
    jmp .loop_label

.over_print:
    pop eax
    ret
CodeALen    equ    $ - LABEL_CODE_A

; 16 位代码段. 由 32 位代码段跳入, 跳出后到实模式
[SECTION .s16code]
ALIGN    32
[BITS    16]
LABEL_SEG_CODE16:
    ; 跳回实模式:
    mov    ax, SelectorNormal
    mov    ds, ax
    mov    es, ax
    mov    fs, ax
    mov    gs, ax
    mov    ss, ax

    mov    eax, cr0
    and    al, 0feh
    mov    cr0, eax

LABEL_GO_BACK_TO_REAL:
    jmp word 0:LABEL_REAL_ENTRY    ; 段地址会在程序开始处被设置成正确的值

Code16Len    equ    $ - LABEL_SEG_CODE16
; END of [SECTION .s16code]

8. 参考资料

https://en.wikipedia.org/wiki/Global\_Descriptor\_Table。 https://software.intel.com/sites/default/files/managed/7c/f1/253668-sdm-vol-3a.pdf#page=95。 《Orange’s》。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-01-31,如有侵权请联系 cloudcommunity@tencent.com 删除
目录
  • 1. 引言
  • 2. 局部描述符表 LDT
  • 3. 描述符结构
  • 4. 实战 LDT
    • 4.1. 创建代码段,编写代码
    • 4.2. 创建 LDT
    • 4.3. 在 GDT 中创建指向 LDT 的描述符及选择子
    • 4.4. 初始化 GDT 中指向 LDT 的描述符的段基址及 LDT 代码描述符段基址
    • 4.5. 修改实地址模式进入保护模式初始代码,跳转到 LDT
  • 5. 执行结果
  • 6. 后记
  • 7. 附录 — 完整代码
  • 8. 参考资料
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档