练习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相关推荐

  1. 操作系统实验报告11:ucore Lab 2

    ucore实验报告2 实验内容 uCore Lab 2:物理内存管理 (1) 编译运行 uCore Lab 2 的工程代码: (2) 完成 uCore Lab 2 练习 1-3 的编程作业: (3) ...

  2. 操作系统实验报告1:ucore Lab 1

    操作系统实验报告1 实验内容 阅读 uCore 实验项目开始文档 (uCore Lab 0),准备实验平台,熟悉实验工具. uCore Lab 1:系统软件启动过程 (1) 编译运行 uCore La ...

  3. 操作系统ucore lab1实验报告

    操作系统lab1实验报告 [练习1] 理解通过 make 生成执行文件的过程.(要求在报告中写出对下述问题的回答) 在此练习中,大家需要通过阅读代码来了解: 1. 操作系统镜像文件 ucore.img ...

  4. 《ucore lab1 练习5》实验报告

    [练习5]实现函数调用堆栈跟踪函数 我们需要在lab1中完成kdebug.c中函数print_stackframe的实现,可以通过函数print_stackframe来跟踪函数调用堆栈中记录的返回地址 ...

  5. 《ucore lab1 exercise5》实验报告

    资源 ucore在线实验指导书 我的ucore实验代码 题目:实现函数调用堆栈跟踪函数 我们需要在lab1中完成kdebug.c中函数print_stackframe的实现,可以通过函数print_s ...

  6. ucore lab1 实验报告

    UCORE LAB1 实验报告 练习一 理解通过make生成执行文件的过程 1.操作系统镜像文件ucore.img是如何一步一步生成的? 先打开lab1文件夹下的Makefile,查看里面的代码,在各 ...

  7. ucore lab3实验报告

    Lab3实验报告 Lab3实验报告 练习0填写以有实验 练习1给未被映射的地址映上物理页 问题回答 练习2补充完成基于FIFO的页面替换算法 问题回答 实验运行截图 扩展练习 Challenge 借助 ...

  8. ucore操作系统实验笔记 - Lab1

    最近一直都在跟清华大学的操作系统课程,这个课程最大的特点是有一系列可以实战的操作系统实验.这些实验总共有8个,我在这里记录实验中的一些心得和总结. Task1 这个Task主要是为了熟悉Makfile ...

  9. 【软件构造】LAB1实验报告

    2022年春季学期 计算学部<软件构造>课程 Lab 1实验报告 姓名 李** 学号 班号 电子邮件 手机号码 目录 1 实验目标概述... 1 2 实验环境配置... 1 3 实验过程. ...

最新文章

  1. AI黑科技:呵护地球,我们是认真的
  2. Oracle 原理:临时表空间的操作方式
  3. jupyter 数据分析可视化案例_Python数据分析及可视化实例之Anaconda、Jupyter简介
  4. java 循环 基本类型
  5. [转载] python中 堆heapq以及 队列queue的使用
  6. android 获取emui版本,华为手机为什么有EMUI版本和Android版本?
  7. 算法题——投篮比赛获胜概率问题
  8. 透析QTP自动化测试框架SAFFRON
  9. mysql 通达信公式_通达信的几个好用指标
  10. Linux Oracle卸载步骤
  11. Mask-rcnn算法流程图
  12. 如何选择合适的离心机,有哪些重要参数——TFN FUP LB6CM 落地式低速大容量冷冻离心机
  13. 读《如何阅读一本书》乱摘
  14. iOS常见崩溃以及总结
  15. PDF怎么加页码?PDF添加页码的方法
  16. 数据结构与算法分析:实现list【理解 iter++ 和 ++iter 】
  17. 做软件工程师需要具备怎样的能力和素质
  18. 电脑剪切后丢失的文件怎么恢复
  19. 事件坐标:screenX,clientX,pageX,offsetX的区别
  20. 百度云网盘资源高速下载免登录网页版教程分享

热门文章

  1. 为什么模板函数应该定义在头文件内
  2. php统计男女数量,我国人口突破14亿!2020中国总人口数量及男女比例公布 头寸管理...
  3. 技术动态 | 利用知识图谱克服人工智能幻觉
  4. XP和WIN7 在不关闭防火墙的情况下开通部分端口
  5. 解读JAVAEE是什么样的Java
  6. 对于JAVAEE的理解
  7. PyGame贪吃蛇的实现
  8. macOS系统下配置JMeter资源监控插件教程(附插件资源下载)
  9. Excel数据透视表切换老版样式
  10. 龙珠机器学习训练营机器学习基础知识笔记