ucore实验报告lab1
练习1
1、生成操作系统镜像文件ucore.img
生成ucore.imge的代码如下:
$(UCOREIMG): $(kernel) $(bootblock)$(V)dd if=/dev/zero of=$@ count=10000$(V)dd if=$(bootblock) of=$@ conv=notrunc$(V)dd if=$(kernel) of=$@ seek=1 conv=notrunc
$(call create_target,ucore.img)
用zero块设备作为输入生成一个大小为10000B的块
然后先将bootblock即BootLoader写入块中接着是kernel
输入“make V=”
+ cc kern/init/init.c
+ cc kern/libs/readline.c
+ cc kern/libs/stdio.c
+ cc kern/debug/kdebug.c
+ cc kern/debug/kmonitor.c
+ cc kern/debug/panic.c
+ cc kern/driver/clock.c
+ cc kern/driver/console.c
+ cc kern/driver/intr.c
+ cc kern/driver/picirq.c
+ cc kern/trap/trap.c
+ cc kern/trap/trapentry.S
+ cc kern/trap/vectors.S
+ cc kern/mm/pmm.c
+ cc libs/printfmt.c
+ cc libs/string.c
+ ld bin/kernel
+ cc boot/bootasm.S
+ cc boot/bootmain.c
+ cc tools/sign.c
+ ld bin/bootblock
第140~151行是生成kernel文件。
kernel编译链接:
# kernel中头文件目录
KINCLUDE += kern/debug/ \kern/driver/ \kern/trap/ \kern/mm/# kernel的源代码目录
KSRCDIR += kern/init \kern/libs \kern/debug \kern/driver \kern/trap \kern/mm# 在编译选项中添加头文件包含目录
KCFLAGS += $(addprefix -I,$(KINCLUDE))# 调用function.mk中的add_files_cc函数,将kernel的全部源文件编译,将源文件和编译生成的OBJ文件加入kernel包(packet)中
$(call add_files_cc,$(call listf_cc,$(KSRCDIR)),kernel,$(KCFLAGS))# 将KOBJS定义为编译生成的.o文件列表(大概??)
KOBJS = $(call read_packet,kernel libs)# create kernel target
# (在kernel前面加上bin/目录名)
kernel = $(call totarget,kernel)# kernel目标依赖于tools/kernel.ld文件
$(kernel): tools/kernel.ld# kernel目标依赖于编译生成的OBJ文件
$(kernel): $(KOBJS)# 输出"+ ld bin/kernel"到控制台@echo + ld $@# 即命令"ld -m elf_i386 -nostdlib -T tools/kernel.ld -o bin/kernel obj/kern/init/init.o ... obj/libs/printfmt.o"$(V)$(LD) $(LDFLAGS) -T tools/kernel.ld -o $@ $(KOBJS)# 将OBJ文件全部反编译为汇编文件@$(OBJDUMP) -S $@ > $(call asmfile,kernel)# 输出OBJ文件对应的符号表@$(OBJDUMP) -t $@ | $(SED) '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $(call symfile,kernel)# 将kernel包和OBJ文件添加到目标依赖
$(call create_target,kernel)
bootblock:
# -------------------------------------------------------------------# create bootblock
# bootfiles为boot/文件夹下的全部文件列表
bootfiles = $(call listf_cc,boot)
# 编译boot/文件夹下的全部文件
$(foreach f,$(bootfiles),$(call cc_compile,$(f),$(CC),$(CFLAGS) -Os -nostdinc))# (在bootblock前面加上bin/目录名)
bootblock = $(call totarget,bootblock)# bootblock目标的依赖项为源文件对应的OBJ文件和bin/sign
$(bootblock): $(call toobj,$(bootfiles)) | $(call totarget,sign)# 输出"+ ld bin/bootblock"到控制台@echo + ld $@# 将OBJ文件链接为bin/bootblock$(V)$(LD) $(LDFLAGS) -N -e start -Ttext 0x7C00 $^ -o $(call toobj,bootblock)# 将bin/bootblock文件反编译@$(OBJDUMP) -S $(call objfile,bootblock) > $(call asmfile,bootblock)# 将bin/bootblock转换成bin/bootblock.out二进制文件@$(OBJCOPY) -S -O binary $(call objfile,bootblock) $(call outfile,bootblock)# 用链接出的bin/sign工具将bin/bootblock.out再转换回bin/bootblock二进制文件@$(call totarget,sign) $(call outfile,bootblock) $(bootblock)# 将bootblock包添加到bootblock目标
$(call create_target,bootblock)
题目3的解答
大小为512字节
最后两个字节为0x55AA
练习二
先看看 Makefile 文件里面都需要干哪些事情。
我们在 /home/moocos/ucore_lab/labcodes_answer/lab1_result 目录下使用 less Makefile 命令去浏览 Makefile 文件中的内容,通过 /lab1-mon 去定位到相应行数的代码(这里我们是201行)。
lab1-mon: $(UCOREIMG)$(V)$(TERMINAL) -e "$(QEMU) -S -s -d in_asm -D $(BINDIR)/q.log -monitor stdio -hda $< -serial null" -g -monitor stdio -hda $< -serial null"$(V)sleep 2$(V)$(TERMINAL) -e "gdb -q -x tools/lab1init"
我们可以看到这条命令大概干了两件事情:
第一个是让 qemu 把它执行的指令给记录下来,放到 q.log 这个地方
第二个是和 gdb 结合来调试正在执行的 Bootloader
我们看看初始化执行指令中都有哪些内容,我们使用如下命令:
less tools/lab1init
会显示如下内容:
file /bin/kernel
target remote :1234
set architecture i8086
b *0x7c00
continue
x /2i $pc
我们尝试用命令去执行一下 bootloader第一条指令看看效果:
输入make lab1-mon
而这些指令都在哪里呢?
我们可以查看 boot/bootasm.S 文件,可以看到,如下图所示的代码和我们看到 gdb 里面的指令是一样的。
我们已经断到 Bootloader 起始的位置,我们接下来可以让它继续运行。
continue
可以看到效果:
这时候我们可以看到 Bootloader 已经加载进来了。
我们修改tools/gdbinit如下:
set architecture i8086
target remote :1234
在 /home/moocos/ucore_lab/labcodes_answer/lab1_result下执行make debug:
此时CS为0xF000,PC为0xFFF0,内存地址为0xFFFF0
可知,CPU加电后第一条执行位于0xFFFF0,并且第一条指令为长跳转指令
可知,BIOS实例存储在cs:ip为0xf000:0xe05b的位置
使用si命令可对BIOS进行单步跟踪
我们再对 tools/gdbinit 做如下修改:
file obj/bootblock.o
set architecture i8086
target remote :1234
b *0x7c00
continue
在 /home/moocos/ucore_lab/labcodes_answer/lab1_result下执行make debug:
调试发现0x7C00为主引导程序的入口地址,代码与bootasm.S一致
使用ni可进行单步调试
我们再对 tools/gdbinit 做如下修改:
file bin/kernel
set architecture i8086
target remote :1234
b kern_init
continue
在 /home/moocos/ucore_lab/labcodes_answer/lab1_result下执行make debug:
在内核入口处增加断点,可以看到代码停在kern_init函数
练习三
分析bootloader进入保护模式的过程
开启A20门
为何开启A20门?
一开始时A20地址线控制是被屏蔽的(总为0) ,直到系统软件通过一定的IO操作去打开它(参看bootasm.S) 。很显然,在实模式下要访问高端内存区,这个开关必须打开,在保护模式下,由于使用32位地址线,如果A20恒等于0,那么系统只能访问奇数兆的内存,即只能访问0–1M、2-3M、4-5M…,这样无法有效访问所有可用内存。所以在保护模式下,这个开关也必须打开。
如何开启A20?
打开A20 Gate的具体步骤大致如下:
等待8042 Input buffer为空
发送Write 8042 Output Port (P2) 命令到8042 Input buffer
等待8042 Input buffer为空
将8042 Output Port(P2) 对应字节的第2位置1,然后写入8042 Input buffer
打开A20 Gate的功能是在boot/bootasm.S中实现的,下面结合相关代码来分析:代码分为seta20.1和seta20.2两部分,其中seta20.1是往端口0x64写数据0xd1,告诉CPU我要往8042芯片的P2端口写数据;seta20.2是往端口0x60写数据0xdf,从而将8042芯片的P2端口设置为1. 两段代码都需要先读0x64端口的第2位,确保输入缓冲区为空后再进行后续写操作。
seta20.1:inb $0x64, %al # Wait for not busy(8042 input buffer empty).testb $0x2, %aljnz seta20.1movb $0xd1, %al # 0xd1 -> port 0x64outb %al, $0x64 # 0xd1 means: write data to 8042's P2 portseta20.2:inb $0x64, %al # Wait for not busy(8042 input buffer empty).testb $0x2, %aljnz seta20.2movb $0xdf, %al # 0xdf -> port 0x60outb %al, $0x60 # 0xdf = 11011111, means set P2's A20 bit(the 1 bit) to 1
初始化GDT表
boot/bootasm.S中的lgdt gdtdesc把全局描述符表的大小和起始地址共8个字节加载到全局描述符表寄存器GDTR中。从代码中可以看到全局描述符表的大小为0x17 + 1 = 0x18,也就是24字节。由于全局描述符表每项大小为8字节,因此一共有3项,而第一项是空白项,所以全局描述符表中只有两个有效的段描述符,分别对应代码段和数据段。
gdtdesc:.word 0x17 # sizeof(gdt) - 1.long gdt # address gdt
下面的代码给出了全局描述符表的具体内容。共有3项,每项8字节。第1项是空白项,内容为全0. 后面2项分别是代码段和数据段的描述符,它们的base都设置为0,limit都设置为0xffffff,也就是长度均为4G. 代码段设置了可读和可执行权限,数据段设置了可写权限。
gdt:SEG_NULLASM # null segSEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) # code seg for bootloader and kernelSEG_ASM(STA_W, 0x0, 0xffffffff) # data seg for bootloader and kernel
SEG_ASM的定义如下
#define SEG_ASM(type,base,lim) \.word (((lim) >> 12) & 0xffff), ((base) & 0xffff); \.byte (((base) >> 16) & 0xff), (0x90 | (type)), \(0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff)
如何使能和进入保护模式
将cr0寄存器的PE位(cr0寄存器的最低位)设置为1,便使能和进入保护模式了。代码如下所示:
movl %cr0, %eaxorl $CR0_PE_ON, %eaxmovl %eax, %cr0
练习四
分析bootloader加载ELF格式的OS的过程
进入保护模式之后,Bootloader 需要干的很重要的一件事就是加载 ELF 文件。因为我们的 kernel(也就是ucore OS)是以 ELF 文件格式存在硬盘上的。
[~/moocos/ucore_lab/labcodes_answer/lab1_result]
moocos-> file bin/kernel
bin/kernel: ELF 32-bit LSB executable, Intel 80386, version 1(SYSV), statically linked, not stripped
定义ELF头指针,指向0x10000
读取8个扇区大小的ELF头到内存地址0x10000
校验ELF header中的模数,判断是否为0x464C457FU
读取ELF header中的程序段到内存中
跳转到操作系统入口
定义ELF头指针,指向0x10000
读取8个扇区大小的ELF头到内存地址0x10000
校验ELF header中的模数,判断是否为0x464C457FU
读取ELF header中的程序段到内存中
跳转到操作系统入口
Bootloader 如何把 ucore 加载到内存中去呢?它需要完成如下的两步操作:
bootloader如何读取硬盘扇区的
bootloader是如何加载ELF格式的OS
执行完bootasm.S后,系统进入保护模式, 进行bootmain.c开始加载OS
定义ELF头指针,指向0x10000
读取8个扇区大小的ELF头到内存地址0x10000
校验ELF header中的模数,判断是否为0x464C457FU
读取ELF header中的程序段到内存中
跳转到操作系统入口
bootloader如何读取硬盘扇区的
bootloader是如何加载ELF格式的OS
bootloader如何读取硬盘扇区的
- bootloader进入保护模式并载入c程序bootmain
- bootmain中readsect函数完成读取磁盘扇区的工作,函数传入一个指针和一个uint_32类型secno,函数将secno对应的扇区内容拷贝至指针处
- 调用waitdisk函数等待地址0x1F7中低8、7位变为0,1,准备好磁盘
- 向0x1F2输出1,表示读1个扇区,0x1F3输出secno低8位,0x1F4输出secno的815位,0x1F5输出secno的1623位,0x1F6输出0xe+secno的24~27位,第四位0表示主盘,第六位1表示LBA模式,0x1F7输出0x20
- 调用waitdisk函数等待磁盘准备好
- 调用insl函数把磁盘扇区数据读到指定内存
bootloader是如何加载ELF格式的OS
bootloader通过bootmain函数完成ELF格式OS的加载。
- 调用readseg函数从kernel头读取8个扇区得到elfher
- 判断elfher的成员变量magic是否等于ELF_MAGIC,不等则进入bad死循环
- 相等表明是符合格式的ELF文件,循环调用readseg函数加载每一个程序段
- 调用elfher的入口指针进入OS
练习五
实现函数调用堆栈跟踪函数
代码实现
编程前,首先了解下当前情况:在Terminal下输入make qemu,发现打印以下信息后就退出了:
along:~/src/ucore/labcodes/lab1$ sudo make qemu
WARNING: Image format was not specified for 'bin/ucore.img' and probing guessed raw.Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.Specify the 'raw' format explicitly to remove the restrictions.
(THU.CST) os is loading ...Special kernel symbols:entry 0x00100000 (phys)etext 0x001036f3 (phys)edata 0x0010e950 (phys)end 0x0010fdc0 (phys)
Kernel executable memory footprint: 64KB
分析print_stackframe的函数调用关系
kern_init ->grade_backtrace ->grade_backtrace0(0, (int)kern_init, 0xffff0000) ->grade_backtrace1(0, 0xffff0000) ->grade_backtrace2(0, (int)&0, 0xffff0000, (int)&(0xffff0000)) ->mon_backtrace(0, NULL, NULL) ->print_stackframe ->
首先定义两个局部变量ebp、esp分别存放ebp、esp寄存器的值。这里将ebp定义为指针,是为了方便后面取ebp寄存器的值。
调用read_ebp函数来获取执行print_stackframe函数时ebp寄存器的值,这里read_ebp必须定义为inline函数,否则获取的是执行read_ebp函数时的ebp寄存器的值。
调用read_eip函数来获取当前指令的位置,也就是此时eip寄存器的值。这里read_eip必须定义为常规函数而不是inline函数,因为这样的话在调用read_eip时会把当前指令的下一条指令的地址(也就是eip寄存器的值)压栈,那么在进入read_eip函数内部后便可以从栈中获取到调用前eip寄存器的值。
由于变量eip存放的是下一条指令的地址,因此将变量eip的值减去1,得到的指令地址就属于当前指令的范围了。由于只要输入的地址属于当前指令的起始和结束位置之间,print_debuginfo都能搜索到当前指令,因此这里减去1即可。
以后变量eip的值就不能再调用read_eip来获取了(每次调用获取的值都是相同的),而应该从ebp寄存器指向栈中的位置再往上一个单位中获取。
由于ebp寄存器指向栈中的位置存放的是调用者的ebp寄存器的值,据此可以继续顺藤摸瓜,不断回溯,直到ebp寄存器的值变为0
void print_stackframe(void) {uint32_t *ebp = 0;uint32_t esp = 0;ebp = (uint32_t *)read_ebp();esp = read_eip();while (ebp){cprintf("ebp:0x%08x eip:0x%08x args:", (uint32_t)ebp, esp);cprintf("0x%08x 0x%08x 0x%08x 0x%08x\n", ebp[2], ebp[3], ebp[4], ebp[5]);print_debuginfo(esp - 1);esp = ebp[1];ebp = (uint32_t *)*ebp;}/* LAB1 YOUR CODE : STEP 1 *//* (1) call read_ebp() to get the value of ebp. the type is (uint32_t);* (2) call read_eip() to get the value of eip. the type is (uint32_t);* (3) from 0 .. STACKFRAME_DEPTH* (3.1) printf value of ebp, eip* (3.2) (uint32_t)calling arguments [0..4] = the contents in address (uint32_t)ebp +2 [0..4]* (3.3) cprintf("\n");* (3.4) call print_debuginfo(eip-1) to print the C calling function name and line number, etc.* (3.5) popup a calling stackframe* NOTICE: the calling funciton's return addr eip = ss:[ebp+4]* the calling funciton's ebp = ss:[ebp]*/
}
最后一行是ebp:0x00007bf8 eip:0x00007d6e args:0xc031fcfa 0xc08ed88e 0x64e4d08e 0xfa7502a8,共有ebp,eip和args三类参数,下面分别给出解释。
ebp:0x0007bf8 此时ebp的值是kern_init函数的栈顶地址,从obj/bootblock.asm文件中知道整个栈的栈顶地址为0x00007c00,ebp指向的栈位置存放调用者的ebp寄存器的值,ebp+4指向的栈位置存放返回地址的值,这意味着kern_init函数的调用者(也就是bootmain函数)没有传递任何输入参数给它!因为单是存放旧的ebp、返回地址已经占用8字节了。
eip:0x00007d6e eip的值是kern_init函数的返回地址,也就是bootmain函数调用kern_init对应的指令的下一条指令的地址。这与obj/bootblock.asm是相符合的。
args:0xc031fcfa 0xc08ed88e 0x64e4d08e 0xfa7502a8 一般来说,args存放的4个dword是对应4个输入参数的值。但这里比较特殊,由于bootmain函数调用kern_init并没传递任何输入参数,并且栈顶的位置恰好在boot loader第一条指令存放的地址的上面,而args恰好是kern_int的ebp寄存器指向的栈顶往上第2~5个单元,因此args存放的就是boot loader指令的前16个字节!可以对比obj/bootblock.asm文件来验证(验证时要注意系统是小端字节序)。
00007c00 <start>:7c00: fa cli 7c01: fc cld 7c02: 31 c0 xor %eax,%eax7c04: 8e d8 mov %eax,%ds7c06: 8e c0 mov %eax,%es7c08: 8e d0 mov %eax,%ss7c0a: e4 64 in $0x64,%al7c0c: a8 02 test $0x2,%al7c0e: 75 fa jne 7c0a <seta20.1>
练习六
完善中断初始化和处理
ucore实验报告lab1相关推荐
- 操作系统实验报告11:ucore Lab 2
ucore实验报告2 实验内容 uCore Lab 2:物理内存管理 (1) 编译运行 uCore Lab 2 的工程代码: (2) 完成 uCore Lab 2 练习 1-3 的编程作业: (3) ...
- 操作系统实验报告1:ucore Lab 1
操作系统实验报告1 实验内容 阅读 uCore 实验项目开始文档 (uCore Lab 0),准备实验平台,熟悉实验工具. uCore Lab 1:系统软件启动过程 (1) 编译运行 uCore La ...
- 操作系统ucore lab1实验报告
操作系统lab1实验报告 [练习1] 理解通过 make 生成执行文件的过程.(要求在报告中写出对下述问题的回答) 在此练习中,大家需要通过阅读代码来了解: 1. 操作系统镜像文件 ucore.img ...
- 《ucore lab1 练习5》实验报告
[练习5]实现函数调用堆栈跟踪函数 我们需要在lab1中完成kdebug.c中函数print_stackframe的实现,可以通过函数print_stackframe来跟踪函数调用堆栈中记录的返回地址 ...
- 《ucore lab1 exercise5》实验报告
资源 ucore在线实验指导书 我的ucore实验代码 题目:实现函数调用堆栈跟踪函数 我们需要在lab1中完成kdebug.c中函数print_stackframe的实现,可以通过函数print_s ...
- ucore lab1 实验报告
UCORE LAB1 实验报告 练习一 理解通过make生成执行文件的过程 1.操作系统镜像文件ucore.img是如何一步一步生成的? 先打开lab1文件夹下的Makefile,查看里面的代码,在各 ...
- ucore lab3实验报告
Lab3实验报告 Lab3实验报告 练习0填写以有实验 练习1给未被映射的地址映上物理页 问题回答 练习2补充完成基于FIFO的页面替换算法 问题回答 实验运行截图 扩展练习 Challenge 借助 ...
- ucore操作系统实验笔记 - Lab1
最近一直都在跟清华大学的操作系统课程,这个课程最大的特点是有一系列可以实战的操作系统实验.这些实验总共有8个,我在这里记录实验中的一些心得和总结. Task1 这个Task主要是为了熟悉Makfile ...
- 【软件构造】LAB1实验报告
2022年春季学期 计算学部<软件构造>课程 Lab 1实验报告 姓名 李** 学号 班号 电子邮件 手机号码 目录 1 实验目标概述... 1 2 实验环境配置... 1 3 实验过程. ...
最新文章
- AI黑科技:呵护地球,我们是认真的
- Oracle 原理:临时表空间的操作方式
- jupyter 数据分析可视化案例_Python数据分析及可视化实例之Anaconda、Jupyter简介
- java 循环 基本类型
- [转载] python中 堆heapq以及 队列queue的使用
- android 获取emui版本,华为手机为什么有EMUI版本和Android版本?
- 算法题——投篮比赛获胜概率问题
- 透析QTP自动化测试框架SAFFRON
- mysql 通达信公式_通达信的几个好用指标
- Linux Oracle卸载步骤
- Mask-rcnn算法流程图
- 如何选择合适的离心机,有哪些重要参数——TFN FUP LB6CM 落地式低速大容量冷冻离心机
- 读《如何阅读一本书》乱摘
- iOS常见崩溃以及总结
- PDF怎么加页码?PDF添加页码的方法
- 数据结构与算法分析:实现list【理解 iter++ 和 ++iter 】
- 做软件工程师需要具备怎样的能力和素质
- 电脑剪切后丢失的文件怎么恢复
- 事件坐标:screenX,clientX,pageX,offsetX的区别
- 百度云网盘资源高速下载免登录网页版教程分享