操作系统实验ucorelab1Word格式文档下载.docx
- 文档编号:17342741
- 上传时间:2022-12-01
- 格式:DOCX
- 页数:13
- 大小:767.51KB
操作系统实验ucorelab1Word格式文档下载.docx
《操作系统实验ucorelab1Word格式文档下载.docx》由会员分享,可在线阅读,更多相关《操作系统实验ucorelab1Word格式文档下载.docx(13页珍藏版)》请在冰豆网上搜索。
输入b*0x7c10设置断点,输入c执行到断点。
输入x/10i$pc查看最近10条指令的反汇编内容。
输入stepi,单步执行一条机器指令。
再一次输入x/10i$pc查看最近10条指令的反汇编内容。
最后,输入c,qemu继续工作。
三、分析bootloader进入保护模式的过程
从%cs=0$,pc=0x7c00进入bootloader。
.globlstart
start:
准备:
将中断标志位清0,不允许中断,设置增址,将段寄存器置0。
.code16
cli
cld
xorw%ax,%ax
movw%ax,%ds
movw%ax,%es
movw%ax,%ss
开启A20:
通过将键盘控制器上的A20线置于高电位,使全部32条地址线可用,进而可以访问4G的内存空间。
seta20.1:
#等待8042键盘控制器不忙
inb$0x64,%al
testb$0x2,%al
jnzseta20.1
movb$0xd1,%al#发送写8042输出端口的指令
outb%al,$0x64
seta20.2:
inb$0x64,%al
jnzseta20.2
movb$0xdf,%al#打开A20
outb%al,$0x60
加载GDTR、初始化GDT表:
GDT表和其描述符已经静态储存在引导区中,载入即可。
lgdtgdtdesc
进入保护模式:
通过将cr0寄存器PE位置1便开启了保护模式。
movl%cr0,%eax
orl$CR0_PE_ON,%eax
movl%eax,%cr0
通过长跳转更新cs的基地址。
ljmp$PROT_MODE_CSEG,$protcseg
.code32
protcseg:
设置段寄存器,并建立堆栈。
movw$PROT_MODE_DSEG,%ax
movw%ax,%ds
movw%ax,%es
movw%ax,%fs
movw%ax,%gs
movw%ax,%ss
movl$0x0,%ebp
movl$start,%esp
完成进入保护模式,进入bootmain。
callbootmain
四、分析bootloader加载ELF格式的OS的过程
a)bootloader读取硬盘扇区
readsect函数可以从设备的第secno扇区读取数据到dst位置。
设置读取扇区的数目为、扇区号,读取扇区。
outb(0x1F2,1);
outb(0x1F3,secno&
0xFF);
outb(0x1F4,(secno>
>
8)&
outb(0x1F5,(secno>
16)&
outb(0x1F6,((secno>
24)&
0xF)|0xE0);
outb(0x1F7,0x20);
读取一个扇区。
insl(0x1F0,dst,SECTSIZE/4);
readseg函数包装了readsect,使得可以从设备读取任意长度的内容。
b)bootloader加载ELF格式的OS
读取ELF的头部。
readseg((uintptr_t)ELFHDR,SECTSIZE*8,0);
判断是否是合法的ELF文件。
if(ELFHDR->
e_magic!
=ELF_MAGIC){
gotobad;
}
根据ELFheader和proghdr程序头,读出代码段和数据段,并且加载到相应地方。
ph=(structproghdr*)((uintptr_t)ELFHDR+ELFHDR->
e_phoff);
eph=ph+ELFHDR->
e_phnum;
for(;
ph<
eph;
ph++){
readseg(ph->
p_va&
0xFFFFFF,ph->
p_memsz,ph->
p_offset);
根据ELF头部储存的入口信息,找到内核的入口。
((void(*)(void))(ELFHDR->
e_entry&
0xFFFFFF))();
五、实现函数调用堆栈跟踪函数
因为ss:
ebp指向的堆栈位置储存着跳转之前的ebp,所有,以此为线索可以得到所有使用堆栈的函数ebp。
ss:
ebp+4指向的是调用时的eip,ss:
ebp+8等是参数。
又因为bootloader设置的堆栈从0x7c00开始,使用"
callbootmain"
转入bootmain函数,所以,堆栈最深一层值为ebp:
0x00007bf8eip:
0x00007d68。
代码分析:
得到当前ebp,eip。
uint32_tebp=read_ebp(),eip=read_eip();
输出ebp,eip。
cprintf("
ebp:
0x%08xeip:
0x%08xargs:
"
ebp,eip);
设置指针,输出四个参数。
uint32_t*args=(uint32_t*)ebp+2;
for(j=0;
j<
4;
j++){
cprintf("
0x%08x"
args[j]);
调用print_debuginfo函数完成查找对应函数名并打印至屏幕。
print_debuginfo(eip-1);
获取上一层eip,ebp。
eip=((uint32_t*)ebp)[1];
ebp=((uint32_t*)ebp)[0];
六、完善中断初始化和处理
a)中断向量表中一个表项占多少字节?
其中哪几位代表中断处理代码的入口?
中断向量表一个表项占用8字节,其中2-3字节是段选择子,0-1字节和6-7字节拼成位移,两者联合便是中断处理程序的入口地址。
b)完善对中断向量表进行初始化的函数idt_init
获得IDT表的入口地址。
externuintptr_t__vectors[];
在中断门描述符表建立中断门描述符,其中存储了中断处理例程的代码段GD_KTEXT和偏移量__vectors[i],特权级为DPL_KERNEL。
通过查询idt[i]就可定位到中断服务例程的起始地址。
for(i=0;
i<
sizeof(idt)/sizeof(structgatedesc);
i++){
SETGATE(idt[i],0,GD_KTEXT,__vectors[i],DPL_KERNEL);
}
通过指令lidt把中断门描述符表的起始地址装入IDTR寄存器中。
lidt(&
idt_pd);
c)完善对时钟中断进行处理的部分
当ticks每加100次后(大约1秒),输出“100ticks”。
ticks++;
if(ticks%TICK_NUM==0){
print_ticks();
七、扩展练习
a)从内核空间切换到用户空间
调用lab1_switch_to_user函数进行从内核空间到用户空间的切换,lab1_switch_to_user函数内容如下:
asmvolatile(
从中断返回时,会多pop两位,并用这两位的值更新ss、sp,
所以要先把栈压两位。
"
sub$0x8,%%esp\n"
调用T_SWITCH_TOU号中断。
int%0\n"
修复esp。
movl%%ebp,%%esp"
:
i"
(T_SWITCH_TOU));
调用T_SWITCH_TOU号中断代码如下:
if(tf->
tf_cs!
=USER_CS){
设置switchk2u。
switchk2u=*tf;
将cs,ds,es,ss设置为用户态。
switchk2u.tf_cs=USER_CS;
switchk2u.tf_ds=switchk2u.tf_es=switchk2u.tf_ss=USER_DS;
设置esp值。
switchk2u.tf_esp=(uint32_t)tf+sizeof(structtrapframe)-8;
设置eflags值。
switchk2u.tf_eflags|=FL_IOPL_MASK;
设置新栈顶指向switchk2u,当返回出栈,则出栈switchk2u中的值。
*((uint32_t*)tf-1)=(uint32_t)&
switchk2u;
b)从用户空间切换到内核空间
调用lab1_switch_to_kernel函数进行从内核空间到用户空间的切换,lab1_switch_to_kernel函数内容如下:
调用T_SWITCH_TOK号中断。
从中断返回时,esp仍在TSS指示的堆栈中。
所以要在从中断返回后修复esp。
(T_SWITCH_TOK));
调用T_SWITCH_TOK号中断代码如下:
=KERNEL_CS){
将cs,ds,es设置为内核态。
tf->
tf_cs=KERNEL_CS;
tf_ds=tf->
tf_es=KERNEL_DS;
tf_eflags&
=~FL_IOPL_MASK;
设置switchu2k值。
switchu2k=(structtrapframe*)(tf->
tf_esp-(sizeof(structtrapframe)-8));
用tf值覆盖switchu2k值。
memmove(switchu2k,tf,sizeof(structtrapframe)-8);
设置新栈顶指向switchk2u,当返回出栈,则出栈switchk2u中的值。
*((uint32_t*)tf-1)=(uint32_t)switchu2k;
八、问题
a)配置环境
我首先主要遇到的问题是安装一个代码阅读的工具,因为代码量比较大而且函数间相互调用很多。
最后安装了understand,首先下载软件压缩包,然后解压到安装目录,再添加路径,最后输入证书号。
还有一个是在运行makeqemu时报错,解决办法是:
建立符号链接文件”ln-s/usr/local/bin/qenu-system-i386/usr/local/bin/qemu”。
b)在练习2时,设置断点在0x7c20时,没有在断点处停止
后来才了解到设置断点时,断点处的信息会被修改,有时由于设置断点刚好设置在一条指令中间,这样信息修改后,原指令一部分未被覆盖,与断点的机器码可能恰好组成其他的指令,所以没有停止。
所以,设置断点时应该注意要设置在一条指令完成之后,而不要设置在一条指令中间。
c)在练习4,无法理解readseg((uintptr_t)ELFHDR,SECTSIZE*8,0)
在询问后知道,这句代码并不是获取bootloader,而是获取读取ELF文件的头部,所以大小不是512字节,应该是512*8字节,也就是一页4k。
d)在练习5,无法理解堆栈最深一层值为ebp:
0x00007bf8
查阅后知道,在编译器会在每个函数体之前插入
pushl%ebp
movl%esp,%ebp
所以堆栈最深一层ebp为0x7c00-0008=7bf8。
e)在扩展练习中,无法理解"
sub$0x8,%%esp"
和"
讲解后知道,"
是因为,从中断返回时,会多pop两位,并用这两位的值更新ss、sp,所以要先把栈压两位。
是因为,从中断返回时,esp仍在TSS指示的堆栈中,所以要在从中断返回后修复esp。
f)在扩展练习中,如何只使用一个栈空间实现从内核空间到用户空间的切换
修改代码如下,但是报错,不知道问题在哪里。
caseT_SWITCH_TOU:
if(tf->
tf->
tf_cs=USER_CS;
tf_es=tf->
tf_ss=USER_DS;
tf_esp=(uint32_t)tf+sizeof(structtrapframe)-8;
tf_eflags|=FL_IOPL_MASK;
tf;
和同学讨论后,发现是最后一句代码有误。
应该为
*((uint32_t*)tf-1)=(uint32_t)tf;
tf本来就是一个指针,不需要再加地址符。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 操作系统 实验 ucorelab1