操作系统实验ucorelab1.docx

上传人:b****5 文档编号:4417365 上传时间:2022-12-01 格式:DOCX 页数:13 大小:767.51KB
下载 相关 举报
操作系统实验ucorelab1.docx_第1页
第1页 / 共13页
操作系统实验ucorelab1.docx_第2页
第2页 / 共13页
操作系统实验ucorelab1.docx_第3页
第3页 / 共13页
操作系统实验ucorelab1.docx_第4页
第4页 / 共13页
操作系统实验ucorelab1.docx_第5页
第5页 / 共13页
点击查看更多>>
下载资源
资源描述

操作系统实验ucorelab1.docx

《操作系统实验ucorelab1.docx》由会员分享,可在线阅读,更多相关《操作系统实验ucorelab1.docx(13页珍藏版)》请在冰豆网上搜索。

操作系统实验ucorelab1.docx

操作系统实验ucorelab1

一、通make生成执行文件的过程

a)操作系统镜像文件ucore.img是如何一步一步生成的

在编译时使用makeV=,相当于设置一个标记,使得把make编译执行的过程全部展示出来。

首先,调用了GCC,把一些C的源代码编译成.O的目标文件。

然后,调用了ld,把这些目标文件准换成一个可执行程序,比如下面示例,转换成为了bootloader的一个执行程序bootblock.out。

最后,调用了dd,把bootloader、bootblock和kernel放到虚拟的硬盘uCore.img里面。

b)硬盘的主引导扇区的特征是什么

在sign.c中,先申请了一个512字节的空间buf,然后将buf初始化为全0,再将主引导程序写入这个空间,最后,在buf的最后两个字节写入55AA。

所以硬盘的主引导扇区的特征是主引导扇区的512个字节的最后两个字节是55AA。

二、使用qemu执行并调试lab1中的软件

a)从启动开始,单步跟踪BIOS的执行

实验远程调用的方法,启动qemu,并让它进入-S状态。

开启另外一个终端,执行gdb命令,绑定端口1234。

在QEMU窗口中使用x/10i$pc查看最近10条指令的反汇编内容。

在gdb中使用si命令执行单步,显示位置。

可以看到一启动,就处于0xfffffff0的位置,第一条指令是个长跳转指令。

b)在初始化位置0x7c00设置实地址断点,测试断点正常

输入b*0x7c00设置断点,输入c执行到断点。

输入x/10i$pc查看最近10条指令的反汇编内容,可以看到qemu执行到断点0x7c00。

断点工作正常。

再次输入c执行,qemu继续工作。

得到结论,断点工作正常。

c)从0x7c00开始跟踪代码运行,将单步跟踪反汇编得到的代码与bootasm.S和bootblock.asm进行比较

经过比较发现:

gdb得到的反汇编代码与bootasm.S和bootblock.asm中的代码基本相一致。

d)找一个bootloader或内核中的代码位置,设置断点并进行测试

输入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:

#等待8042键盘控制器不忙

inb$0x64,%al

testb$0x2,%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)&0xFF);

outb(0x1F5,(secno>>16)&0xFF);

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

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

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函数内容如下:

asmvolatile(

调用T_SWITCH_TOK号中断。

"int%0\n"

从中断返回时,esp仍在TSS指示的堆栈中。

所以要在从中断返回后修复esp。

"movl%%ebp,%%esp"

:

:

"i"(T_SWITCH_TOK));

调用T_SWITCH_TOK号中断代码如下:

if(tf->tf_cs!

=KERNEL_CS){

将cs,ds,es设置为内核态。

tf->tf_cs=KERNEL_CS;

tf->tf_ds=tf->tf_es=KERNEL_DS;

设置eflags值。

tf->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"和"movl%%ebp,%%esp"

讲解后知道,"sub$0x8,%%esp"是因为,从中断返回时,会多pop两位,并用这两位的值更新ss、sp,所以要先把栈压两位。

"movl%%ebp,%%esp"是因为,从中断返回时,esp仍在TSS指示的堆栈中,所以要在从中断返回后修复esp。

f)在扩展练习中,如何只使用一个栈空间实现从内核空间到用户空间的切换

修改代码如下,但是报错,不知道问题在哪里。

caseT_SWITCH_TOU:

if(tf->tf_cs!

=USER_CS){

tf->tf_cs=USER_CS;

tf->tf_ds=tf->tf_es=tf->tf_ss=USER_DS;

tf->tf_esp=(uint32_t)tf+sizeof(structtrapframe)-8;

tf->tf_eflags|=FL_IOPL_MASK;

*((uint32_t*)tf-1)=(uint32_t)&tf;}

和同学讨论后,发现是最后一句代码有误。

应该为

*((uint32_t*)tf-1)=(uint32_t)tf;

tf本来就是一个指针,不需要再加地址符。

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高中教育 > 初中教育

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1