在ELF格式内核中设置GDT、IDT等相关
来源:互联网 发布:nginx部署多个网站 编辑:程序博客网 时间:2024/06/10 15:27
快一个多月了,一直想要在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 kmainpopadiretd ;中断处理函数的返回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)
最后的源代码
- 在ELF格式内核中设置GDT、IDT等相关
- 段、GDT/IDT 相关知识点
- GDT/IDT
- About GDT LDT IDT
- About GDT LDT IDT
- IDT GDT LDT
- GDT和IDT的初始化
- GDT和IDT的初始化
- linux内核之elf格式
- ELF格式以及相关加载
- 在winxp环境下,用windbg查看GDT表、IDT表、TSS描述符
- gdt相关
- 05day 文字显示 GDT/IDT初始化
- 操作系统之GDT和IDT(三)
- minix中的GDT,LDT,IDT和TSS
- linux 0.11 进程调度, 硬件基础 GDT IDT
- 1个人开发操作系统之GDT和IDT的初始化
- LInux 描述符GDT, IDT & LDT结构定义
- android String.XML添加空格、单引号等字符的方法
- android.content.pm.PackageManager$NameNotFoundException: com.google.android.webview错误
- As3基础部分2
- 牛客 | 初级项目课(python) | 学习笔记01
- Unity3D中2DUI跟随场景中3D物体
- 在ELF格式内核中设置GDT、IDT等相关
- 冒泡排序,选择排序,插入排序,快速排序的比较及优化
- linux应用领域
- 拦截器中通过response返回JSON数据
- 51nod 1202 子序列的个数 dp
- hdu-2546 饭卡
- jdk版本冲突Unsupported major.minor version错误定位
- Dividing(多重背包)
- Error:No toolchains found in the NDK toolchains folder for ABI with prefix: arm-linux-androideabi