在前面的分析(0x03节: JOS的启动过程)中,JOS转到ELF格式的kernel的entry处继续运行,那么,这个entry对应的代码是什么呢?
检查链接文件kernel.ld, 第六行内容为:ENTRY(_start); 继续寻找位置_start, 发现定义在entry.S文件中。
文件kernel/entry.S首先定义了宏RELOC,用来将link address为0xf0000000以上的地址空间映射到基地址为0x00000000地址空间。接着设置了ELF的头部内容。
然后,就是我们要找的_start. 该处代码首先重新设置了GDT,并初始化ds,es,ss后,ljmp到紧跟其后的代码,设置好堆栈,然后调用函数i386_init,进入文件kernel/init.c。 此时栈底为0x0,而栈顶指针为变量bootstacktop所在位置。
由于调用函数i386_init时,会将栈底指针ebp指向栈指针esp所在位置,所以进入i386_init()后,栈底为bootstacktop位置处,该位置以下预留了KSTKSIZE=32KB字节大小的空间作为kernel的栈空间。而由于栈里已经压入了几个参数,因此esp在ebs下方不远处。
进入i386_init()函数: 首先将bss清零。其外部变量edata和end均定义在文件kern/kernel.ld中。初始化cga显示,从而也初始化了键盘以及串口。此时方可以使用功能类似于linux中的printk函数的cprintf进行输出。 由于在前面已经实现了八进制的输出功能,因此得到的输出为"6828 decimal is 15254 octal!".
在测试了kernel debug功能代码(尚未实现)后,进入死循环,循环运行monitor. monitor的功能类似于Linux下的shell,当然此时其功能相当简单,仅识别两个命令:help, kerninfo. 让我们看看它的实现。
kern/monitor.c: 输出"K> ", 然后读入用户输入,作为参数由函数runcmd()进行分析。 runcmd将传入的参数以空白字符为分割符进行分割,分别存入变量argv对应的指针数组中,然后在类型为Command的结构数组commands中查找argv[0]中保存的命令是否存在。若存在,则调用结构中对应的指针函数(*func).
函数mon_kerninfo()提供了kernel运行时的一些参数,我们可以分析一下其输出提供的一些信息:
_start f010000c (virt) 0010000c (phys)
etext f01016a0 (virt) 001016a0 (phys)
edata f010f320 (virt) 0010f320 (phys)
end f010f980 (virt) 0010f980 (phys)
Kernel executable memory footprint: 63KB
可知,_start的位置为f010000c, 而kernel的位置为f0100000. 其相差的0x0c个字节用于ELF的header,内容为:
.long MULTIBOOT_HEADER_MAGIC
.long MULTIBOOT_HEADER_FLAGS
.long CHECKSUM
而etext的位置为f01016a0。参考文件obj/kern/kernel.asm可知,这其实是kernel的text段的结尾位置。
对于edata的位置,尚未找到验证的方法。而kernel所用的全部内存大小为(end-_start).
调试:
现在,假如想用bochs单步调试函数i386_init, 则首先通过文件obj/kern/kernel.sym查找符号i386_init对应的位置为f010013a,由于内存映射的缘故,其实际物理地址为0x0010013a. 在运行bochs时,用如下命令设置断点:pb 0x0010013a,然后用命令"c"运行到该点处,然后就能用命令"n"单步调试了。
(转贴请注明: by: chunchengfh, from: chunchengfh.cublog.cn)
阅读(1829) | 评论(0) | 转发(0) |