Orange'S 第三章第一节实验

这个实验主要包含三部分

第一部分是定义全局描述符表gdt,以及全局描述符表指针gdtr
第二部分是16位代码段,主要工作有:
  • 关中断
  • 开启A20
  • 开启保护模式
  • 跳到32位代码段
第三部分是32位代码段,往显存里写一个字母’p’

分析

首先第一部分是纯数据,可以用C语言来实现,代码如下:

desc.h

#include <stdint.h>

// Type field
#define TYPE_DATA_RO          0
#define TYPE_DATA_RO_A        1
#define TYPE_DATA_RW          2
#define TYPE_DATA_RW_A        3
#define TYPE_DATA_RO_E        4
#define TYPE_DATA_RO_E_A      5
#define TYPE_DATA_RW_E        6
#define TYPE_DATA_RW_E_A      7
#define TYPE_CODE_XO          8
#define TYPE_CODE_XO_A        9
#define TYPE_CODE_XR          10
#define TYPE_CODE_XR_A        11
#define TYPE_CODE_XO_C        12
#define TYPE_CODE_XO_C_A      13
#define TYPE_CODE_XR_C        14
#define TYPE_CODE_XR_C_A      15

#define TYPE_SYS_TSS_16_A     1
#define TYPE_SYS_LDT          2
#define TYPE_SYS_TSS_16_B     3
#define TYPE_SYS_CALL_GATE_16 4
#define TYPE_SYS_TASK_GATE    5
#define TYPE_SYS_INT_GATE_16  6
#define TYPE_SYS_TRAP_GATE_16 7
#define TYPE_SYS_TSS_32_A     9
#define TYPE_SYS_TSS_32_B     11
#define TYPE_SYS_CALL_GATE_32 12
#define TYPE_SYS_INT_GATE_32  14
#define TYPE_SYS_TRAP_GATE_32 15

// S(descripor type) flag
#define DESC_TYPE_SYSTEM 0
#define DESC_TYPE_CODE   1
#define DESC_TYPE_DATA   1

// DPL(descriptor privilege level) field
#define DPL_0 0
#define DPL_1 1
#define DPL_2 2
#define DPL_3 3

// P(segment-present) flag
#define SEG_PRESENT     1
#define SEG_NOT_PRESENT 0

// D/B flag
#define CODE_32         1
#define CODE_16         0
#define DATA_32         1
#define DATA_16         0

// G(granularity) flag
#define G_BYTE    0
#define G_4K_BYTE 1

typedef struct __attribute__((packed)) {
    uint16_t limit;
    uint32_t base;
} gdtr_t;

typedef struct {
    uint64_t limit_15_0:16;
    uint64_t base_23_0:24;
    uint64_t type:4;
    uint64_t s:1;
    uint64_t dpl:2;
    uint64_t p:1;
    uint64_t limit_19_16:4;
    uint64_t avl:1;
    uint64_t res:1;
    uint64_t db:1;
    uint64_t g:1;
    uint64_t base_31_24:8;
} seg_desc_t;

#define SEG_DESC(_base, _limit, _type, _s, _dpl, _p, _db, _g) { \
        .base_23_0 = (_base) & 0xffffff,         \
        .base_31_24 = ((_base) >> 24) & 0xff, \
        .limit_15_0 = (_limit) & 0xffff, \
            .limit_19_16 = ((_limit) >> 16) & 0xf, \
            .type = (_type), \
            .s = (_s), \
            .dpl = (_dpl), \
            .p = (_p), \
            .avl = 0, \
            .res = 0, \
            .db = (_db), \
            .g = (_g), \
}
上述代码定义了两个结构,分别是:
  • gdtr_t 表示全局描述符指针
  • seg_desc_t 表示段描述符

boot1.c

#include "desc.h"

seg_desc_t gdt[3] = {
    [0] = SEG_DESC(0, 0, 0, 0, 0, 0, 0, 0),
    [1] = SEG_DESC(0x7c80, 0xff, TYPE_CODE_XO, DESC_TYPE_CODE, DPL_0, SEG_PRESENT, CODE_32, G_BYTE),
    [2] = SEG_DESC(0xb8000, 0xffff, TYPE_DATA_RW, DESC_TYPE_DATA, DPL_0, SEG_PRESENT, DATA_32, G_BYTE),
};

gdtr_t gdtr = {
    .limit = sizeof(gdt)-1,
    .base = (uint32_t)&gdt,
};
上述代码定义了两个对象:
  • 全局描述符表gdt

    按要求全局描述符表的第一描述符是空描述符
    第二个描述符是代码段描述符,基地址是0x7c80,段大小(limit+1)是256字节,在这里够用了
    第三个描述符是数据段描述符,基地址是0xb8000,这个对应的是显存地址,段大小(limit+1)是64KB

  • 全局描述符表指针gdtr

    这个指针有两个字段,base指向全局描述符表,limit指定全局描述符表的界限

ok,第一部分数据定义完成了,下面看16位代码段和32位代码段

16位代码段以及32位代码
#define CODE_SELECTOR 0x8
#define DATA_SELECTOR 0x10
#define CURSOR_POS ((80 * 11 + 79) * 2)

    .code16
    .section text16
    .globl _start
_start:
    cli

    # load gdtr
    lgdt gdtr

    # enable a20
    inb $0x92, %al
    orb $0b10, %al
    outb %al, $0x92

    # enable pm
    movl %cr0, %eax
    orl $0x01, %eax
    movl %eax, %cr0

    # jump to pm
    jmpl $CODE_SELECTOR, $0x0
    hlt

    .code32
    .section text32
    movw $DATA_SELECTOR, %ax
    movw %ax, %gs
    movl $CURSOR_POS, %edi
    movb $0x0c, %ah
    movb $0x70, %al
    movw %ax, %gs:(%edi)

    jmp .

在16位代码段中加载gdtr只需要用lgdt gdtr指令就可以,这个gdtr引用的就是在boot1.c中定义的gdtr。
跳转到32位代码段的指令jmpl $CODE_SELECTOR, $0x0,其中CODE_SELECTOR为8,就是1<<3,就是引用全局描述符表中的第1个描述符,即,代码段。
32位代码段中将指向第2个描述符(就是数据段描述符)的选择子存到gs寄存器中。

程序链接

先写一个链接脚本,如下:
OUTPUT_FORMAT(binary)
ENTRY(_start)

MEMORY {
       boot_region (RX) : org = 0x7c00, len = 512
}

SECTIONS {
     .text : {
         boot0.o(text16)
         . = 64;
         boot1.o(.data*)
         . = 128;
         boot0.o(text32)
         . = 510;
         SHORT(0xaa55)
     } >boot_region

     /DISCARD/ : {
           *(*)
     }
}

首先定义一个内存区,这个内存区是可读(R)可执行(X)的,起始地址为0x7c00,大小为512字节
注意: 32位代码段放到了0x7c00+128字节处,也就是0x7c80处,这就是为什么全局描述符表中的代码段描述符的基地址为0x7c80的原因

Makefile
a.img: boot.bin
    -rm a.img
    bximage -q -mode=create -fd=1.44M a.img
    dd if=boot.bin of=a.img bs=512 count=1 conv=notrunc

boot.bin: boot0.o boot1.o boot.lds
    ld -Tboot.lds boot0.o boot1.o -o boot.bin
    ld -Tboot.lds boot0.o boot1.o --oformat=elf32-i386 -o boot.elf  # for objdump

boot0.o: boot0.S
    gcc -c boot0.S -o boot0.o

boot1.o: boot1.c desc.h
    gcc -c boot1.c -o boot1.o

.PHONY: clean
clean:
    -rm boot0.o boot1.o boot.bin boot.elf a.img

这里链接了两次,分别输出了boot.bin和boot.elf,输出boot.elf是为了使用objdump工具查看反汇编的结果

下面是 objdump -dj .text boot.elf 的内容:

boot.elf:     file format elf32-i386


Disassembly of section .text:

00007c00 <_start>:
    7c00:   fa                      cli    
    7c01:   0f 01 16                lgdtl  (%esi)
    7c04:   58                      pop    %eax
    7c05:   7c e4                   jl     7beb <_start-0x15>
    7c07:   92                      xchg   %eax,%edx
    7c08:   0c 02                   or     $0x2,%al
    7c0a:   e6 92                   out    %al,$0x92
    7c0c:   0f 20 c0                mov    %cr0,%eax
    7c0f:   66 83 c8 01             or     $0x1,%ax
    7c13:   0f 22 c0                mov    %eax,%cr0
    7c16:   66 ea 00 00 00 00       ljmpw  $0x0,$0x0
    7c1c:   08 00                   or     %al,(%eax)
    7c1e:   f4                      hlt    
    ...

00007c40 <gdt>:
    ...
    7c48:   ff 00 80 7c 00 98 40 00 ff ff 00 80 0b 92 40 00     ...|..@.......@.

00007c58 <gdtr>:
    7c58:   17 00 40 7c 00 00 00 00 00 00 00 00 00 00 00 00     ..@|............
    ...
    7c80:   66 b8 10 00 8e e8 bf 7e 07 00 00 b4 0c b0 70 65     f......~......pe
    7c90:   66 89 07 eb fe 00 00 00 00 00 00 00 00 00 00 00     f...............
    ...
    7dfc:   00 00 55 aa                                         ..U.

可以看到:
16位代码段的起始地址为0x7c00
存放gdt和gdtr两个对象的数据段的起始地址为0x7c40
32位代码段的起始地址为0x7c80

运行效果

这里写图片描述

可以看到屏幕中央右侧有一个红色的字母p

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值