在ELF格式内核中设置GDT、IDT等相关

3 篇文章 0 订阅

快一个多月了,一直想要在ELF格式内核中实现中断,参考的是两本书,一本是于渊的orange’s,另一本是川合秀实的30天自制。。。前期,使用的是于渊的方法进入保护模式,加载并运行ELF内核;进入ELF内核后,变使用川合秀实的方式实现了图形界面(仅仅只是显示图形功能),发现各种错误(由其是中断向量号为13的#GP错误,常规保护异常)

因为于渊的方法是,在loader里面加载ELF,然后跳转到ELF执行,跳转过后,在我们编辑代码的时候已经用C语言了,没有办法使用loader汇编文件里面定义的那个GDT,所以于老师为了方便管理,就把原来的GDT复制到内核空间,那样就还可以用。

川合秀实则不一样,前期他用他自己做的工具,跳过了ELF格式,直接可以用C语言编译,链接,所以他第一次建立GDT就是在C语言环境下。这样就不会出现跳转到ELF后再重新建立GDT就不知道当前运行的代码所在的段了。

所以我的做法是,先加载ELF,跳转到ELF内核运行,然后用C语言重新写一个新的GDT和IDT,然后强行跳转到新的GDT里面的某一个代码段运行(这个代码段就当做是内核里面最起始的代码段了),这样,我们运行的程序就在新的GDT里面有对应的描述符可以找到了。

这是刚跳转如ELF内核的汇编代码

kernel_start:
    mov edi, (80 * 11 + 1) * 2    /* 尝试显示一个字符 */
    mov ah, 0x0F
    mov al, 'L'
    mov [gs:edi], ax

;   mov esi, interrupt_handler    /* 这一段代码可以先不管 */
;   mov edi, 0x28000              /* 我是想把中断处理函数复制到内存指定位置 */
;   mov ecx, handler_length
;   handler_cp:
;       cmp ecx, 0
;       jz handler_cp_end
;       dec ecx
;       mov al, byte [cs:esi]
;       mov byte [es:edi], al
;       inc esi
;       inc edi
;       jmp handler_cp
;   handler_cp_end:

    mov esp, Stack0Top    /* 使用新的堆栈 */

    call kstart    /* 跳转到C语言去,准备用C语言建立新的GDT,和一些其它准备工作 */

;------------------------------------------------------------------------------
    /* 在C语言函数kstart里面建立了GDT后,选择子为1*8(对应第一个段描述符)对应的段为
       一个基址为0,范围是整个内存空间的可读可执行的代码段,特权级为0
    */
    jmp 8:now    /* 这行代码极为重要,它使程序强行运行在了新定义的GDT对应的段里面 */
now:
    call kmain    /* 这时我们可以跳转到C语言环境去执行自己想做的事了 */

    mov edi, (80 * 21 + 12) * 2   /* 再次显示字符,看看会不会出错 */
    mov ah, 0x0F
    mov al, '!'
    mov [gs:edi], ax

    mov ax, 9*8    /* 加载TSS,任务状态段,现在还用不到 */
    ltr ax

;   mov edi, (80 * 12 + 4) * 2

    jmp $

上面我觉得最重要的就是jmp 8:now那一行了,它使当前程序强制用新的GDT对应的选择子,这样在后续的程序调用,中断处理等地方就不会出现调用返回时,因为返回的段的选择子在GDT中找不到对应段而出现GP异常了。

现在说说在kstart里面干的事情

void kstart(void)
{
    int i = 1;
    unsigned short *p = (unsigned short *)0xB8000;

    *(p + 80 * 11 + 4) = (0x0A00) | ('A');
    *(p + 80 * 11 + 5) = (0x0B00) | ('l');
    *(p + 80 * 11 + 6) = (0x0C00) | ('l');
    *(p + 80 * 11 + 7) = (0x0D00) | ('e');
    *(p + 80 * 11 + 8) = (0x0E00) | ('n');

    io_cli();
    init_gdt_idt();
    init_pic();

    io_sti();

}

在kstart里面,首先尝试输出几个字符,然后我把中断关闭了,设置了新的GDT和IDT,还有初始化了8259A中断控制器,然后又打开了中断,准备接受中断请求了。

其中的init_gdt_idt是比较重要的,里面定义了几个段和中断门,然后重新加载GDT和IDT

void init_gdt_idt(void)
{
    struct Segment_Descriptor *gdt = (struct Segment_Descriptor *)0x00000800;
    struct Gate_Desciptor     *idt = (struct Gate_Desciptor     *)0x00000000;
    int i;

    for(i = 0; i < 8192; i++)
        set_segdesc(gdt + i, 0, 0, 0);
    for(i = 0; i < 256; i++)
        set_gatedesc(idt + i, (int)(vector_others), 3*8, 0x8E);

    set_segdesc((struct Segment_Descriptor *)(0x00000800 + 1*8), 0xFFFFFFFF, 0x00000000, (DA_32 | DA_CR));  /* 范围是整块内存的代码段 */
    set_segdesc((struct Segment_Descriptor *)(0x00000800 + 2*8), 0xFFFFFFFF, 0x00000000, (DA_32 | DA_DRW)); /* 范围是整块内存的数据段 */
    set_segdesc((struct Segment_Descriptor *)(0x00000800 + 3*8), 0x7FF, (int)stack_ring0, (DA_32 | DA_DRW | DA_DPL0));  /* ring0的堆栈段 */
    set_segdesc((struct Segment_Descriptor *)(0x00000800 + 4*8), 0x7FF, (int)stack_ring1, (DA_32 | DA_DRW | DA_DPL1));  /* ring1的堆栈段 */
    set_segdesc((struct Segment_Descriptor *)(0x00000800 + 5*8), 0x7FF, (int)stack_ring2, (DA_32 | DA_DRW | DA_DPL2));  /* ring2的堆栈段 */
    set_segdesc((struct Segment_Descriptor *)(0x00000800 + 6*8), 0x7FF, (int)stack_ring3, (DA_32 | DA_DRW | DA_DPL3));  /* ring3的堆栈段 */
    set_segdesc((struct Segment_Descriptor *)(0x00000800 + 7*8), 0xFFFF, 0xB8000, (DA_DRW | DA_DPL3));  /* 显存的段 */
    set_segdesc((struct Segment_Descriptor *)(0x00000800 + 8*8), 0xFFFFF, 0x28000, (DA_32 | DA_CR));    /* handler的段 */
    set_segdesc((struct Segment_Descriptor *)(0x00000800 + 9*8), 0x68, (int)LABEL_TSS, DA_386TSS);  /* 用来存放TSS */


    set_gatedesc((struct Gate_Desciptor     *)(0x00000000 + 13*8), (int)(vector13_handler), 1*8, 0x8E);
    set_gatedesc((struct Gate_Desciptor     *)(0x00000000 + 33*8), (int)(vector33_handler), 1*8, 0x8E);

    load_gdt(8191*8, 0x00000800);
    load_idt(255*8, 0x00000000);
}

写GDT描述符和IDT描述符的方法,两位老师的都可以用,只是一个数据结构罢了。
其实,也没做多少事,就跳回汇编程序里面了。
在之后的汇编程序里面则写了中断handler和一些cli,sti等必须用汇编写的代码,给一个中断处理程序的例子:

vector33_handler:
pushad
    mov ax, 7*8
    mov gs, ax
    mov edi, (80 * 12 + 4) * 2
    mov al, 0x61
    out 0x0020, al    ;响应中断请求,为下一次中断做准备
    in al, 0x0060    ;获取键盘读入的键盘编码(不是ASCII码)
    mov dl, al
    mov cl, 8
    .disp_loop:    ;把键盘编码显示出来
        cmp cl, 0
        jz .disp_loop_end
        dec ecx
        mov al, dl
        shr al, cl
        and al, 0x01
        add al, '0'
        mov ah, 0x0B
        mov [gs:edi], ax
        add edi, 2
        jmp .disp_loop
    .disp_loop_end:
    call write_vedio    ;尝试调用函数,看看会不会出错
    call kmain
popad
iretd    ;中断处理函数的返回
nop

在这里,注意处理函数所在的段,我这里把它暂时放到了最开始的段,因为这样就不用在内存中复制代码了,如果想要单独建立一个段来存中断处理函数(一般是要这么做的),就需要把我们的代码复制到那个段所在的位置(包括汇编代码和C语言代码)。
还有一点就是键盘编码,我们通过0x60端口读到的按键数据,不是ASCII码(一开始我以为是ASCII,结果这么也对不上),它是IBM PC键盘扫描码,它是在键盘上每一个键都对应一个8位二进制数(包括小键盘和Fn等,凡是你在键盘上能按到的,都有一个编码,而且按下,按住和弹起对应最高位不同)

以前我在看其它一些工程代码的时候,在最开始的地方总是会有一个start.c和main.c,当时以为两个作用相同,都只是程序刚开始的地方,和成一个写也没关系。但就最近的学习来看,不是的,而是必须写成两个.c文件,一开始的start.c使用C语言编写了一写后面操作要用到的东西,比如GDT,IDT。然后,在start.c的内容执行完后,我们必须要执行一段汇编代码,来初始化一些东西,如把某一个段的内容写进新的GDT对应的段,然后才可以正常跳转到C语言环境继续执行(虽然有时候,我们跳转到start.c后就加一个while1死循环,不会出错,但涉及到段间跳转就可能会出错,这时候就需要那个jmp 8:now)

最后的源代码

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值