ucore lab1 实验报告

ucore lab1 实验报告

练习一

题目1)操作系统镜像文件 ucore.img 是如何一步一步生成的?(需要比较详细地解释 Makefile 中每一条相关命令和命令参数的含义,以及说明命令导致的结果)
题目2)一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?

1)
首先在Makefile文件中找到ucore.img相关代码,可以看出UCOREIMG这个文件依赖于另外两个文件kernel和bootblock。想要生成ucore.img要先生成kernel和bootblock

# create ucore.img
UCOREIMG	:= $(call totarget,ucore.img)
 
$(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)

bootblock,boot目录下bootasm.S和bootmain.c,会进行编译和链接

# create bootblock
bootfiles = $(call listf_cc,boot)
$(foreach f,$(bootfiles),$(call cc_compile,$(f),$(CC),$(CFLAGS) -Os -nostdinc))
 
bootblock = $(call totarget,bootblock)
 
$(bootblock): $(call toobj,$(bootfiles)) | $(call totarget,sign)
	@echo + ld $@
	$(V)$(LD) $(LDFLAGS) -N -e start -Ttext 0x7C00 $^ -o $(call toobj,bootblock)
	@$(OBJDUMP) -S $(call objfile,bootblock) > $(call asmfile,bootblock)
	@$(OBJCOPY) -S -O binary $(call objfile,bootblock) $(call outfile,bootblock)
	@$(call totarget,sign) $(call outfile,bootblock) $(bootblock)
 
$(call create_target,bootblock)

执行的相应代码是:

+ cc boot/bootasm.S
gcc -Iboot/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Os -nostdinc -c boot/bootasm.S -o obj/boot/bootasm.o
+ cc boot/bootmain.c
gcc -Iboot/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Os -nostdinc -c boot/bootmain.c -o obj/boot/bootmain.o
+ cc tools/sign.c
gcc -Itools/ -g -Wall -O2 -c tools/sign.c -o obj/sign/tools/sign.o
gcc -g -Wall -O2 obj/sign/tools/sign.o -o bin/sign

链接部分会制定代码段开始位置 为 0x7c00

ld -m elf_i386 -nostdlib -N -e start -Ttext 0x7C00 obj/boot/bootasm.o obj/boot/bootmain.o -o obj/bootblock.o

kernel

# create kernel target
kernel = $(call totarget,kernel)
 
$(kernel): tools/kernel.ld
 
$(kernel): $(KOBJS)
	@echo + ld $@
	$(V)$(LD) $(LDFLAGS) -T tools/kernel.ld -o $@ $(KOBJS)
	@$(OBJDUMP) -S $@ > $(call asmfile,kernel)
	@$(OBJDUMP) -t $@ | $(SED) '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $(call symfile,kernel)
 
$(call create_target,kernel)

这里会将kern目录下的.c文件进行相应的编译

+ cc kern/init/init.c
gcc -Ikern/init/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/init/init.c -o obj/kern/init/init.o
+ cc kern/libs/readline.c
gcc -Ikern/libs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/libs/readline.c -o obj/kern/libs/readline.o
+ cc kern/libs/stdio.c
gcc -Ikern/libs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/libs/stdio.c -o obj/kern/libs/stdio.o
+ cc kern/debug/kdebug.c
gcc -Ikern/debug/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/debug/kdebug.c -o obj/kern/debug/kdebug.o
+ cc kern/debug/kmonitor.c
gcc -Ikern/debug/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/debug/kmonitor.c -o obj/kern/debug/kmonitor.o
+ cc kern/debug/panic.c
gcc -Ikern/debug/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/debug/panic.c -o obj/kern/debug/panic.o
+ cc kern/driver/clock.c
gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/clock.c -o obj/kern/driver/clock.o
+ cc kern/driver/console.c
gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/console.c -o obj/kern/driver/console.o
+ cc kern/driver/intr.c
gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/intr.c -o obj/kern/driver/intr.o
+ cc kern/driver/picirq.c
gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/picirq.c -o obj/kern/driver/picirq.o
+ cc kern/trap/trap.c
gcc -Ikern/trap/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/trap/trap.c -o obj/kern/trap/trap.o
+ cc kern/trap/trapentry.S
gcc -Ikern/trap/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/trap/trapentry.S -o obj/kern/trap/trapentry.o
+ cc kern/trap/vectors.S
gcc -Ikern/trap/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/trap/vectors.S -o obj/kern/trap/vectors.o
+ cc kern/mm/pmm.c
gcc -Ikern/mm/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/mm/pmm.c -o obj/kern/mm/pmm.o
+ cc libs/printfmt.c
gcc -Ilibs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/  -c libs/printfmt.c -o obj/libs/printfmt.o
+ cc libs/string.c
gcc -Ilibs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/  -c libs/string.c -o obj/libs/string.o

并会通过进行链接

+ ld bin/kernel
ld -m    elf_i386 -nostdlib -T tools/kernel.ld -o bin/kernel  obj/kern/init/init.o obj/kern/libs/readline.o obj/kern/libs/stdio.o obj/kern/debug/kdebug.o obj/kern/debug/kmonitor.o obj/kern/debug/panic.o obj/kern/driver/clock.o obj/kern/driver/console.o obj/kern/driver/intr.o obj/kern/driver/picirq.o obj/kern/trap/trap.o obj/kern/trap/trapentry.o obj/kern/trap/vectors.o obj/kern/mm/pmm.o  obj/libs/printfmt.o obj/libs/string.o

以上就是整个ucore.img生成的大致过程,

总结如下:

1 编译所有生成bin/kernel所需的文件

2 链接生成bin/kernel

3 编译bootasm.S bootmain.c sign.c

4 根据sign规范生成obj/bootblock.o

5 生成ucore.img

2)

观察sign.c文件中的部分源码

char buf[512];              //定义buf数组
memset(buf, 0, sizeof(buf));                           
buf[510] = 0x55;  
buf[511] = 0xAA;  // 把buf数组的最后两位置为    *0x55, 0xAA*
FILE *ofp = fopen(argv[2], "wb+");
size = fwrite(buf, 1, 512, ofp);
if (size != 512) {         //大小为512字节
    fprintf(stderr, "write '%s' error, 
                     size is %d.\n", argv[2], size);
    return -1;
}

则有特征:通过sign对bootblock进行了限制,大小为512字节,多余的字节填0;最后两个字节为0x55,0xAA

练习2

1.从CPU加电后执行的第一条指令开始,单步跟踪BIOS的执行。
2.在初始化位置0x7c00设置实地址断点,测试断点正常。
3.从0x7c00开始跟踪代码运行,将单步跟踪反汇编得到的代码与bootasm.S和bootblock.asm进行比较。

解答
问题1:从CPU加电后执行的第一条指令开始,单步跟踪BIOS的执行

tools/gdbinit的内容如下。可见,这里是对内核代码进行调试,并且将断点设置在内核代码的入口地址,即kern_init函数

file bin/kernel
target remote :1234
break kern_init
continue

为了从CPU加电后执行的第一条指令开始调试,需要修改tools/gdbinit的内容为:

set architecture i8086
file bin/bootblock
target remote :1234
break start
continue

执行make debug,这时会弹出一个QEMU窗口和一个Terminal窗口,这是正常的,因为我们在makefile中定义了debug的操作正是启动QEMU、启动Terminal并在其中运行gdb。

debug: $(UCOREIMG)
( V ) (V) (V)(QEMU) -S -s -parallel stdio -hda $< -serial null &
$(V)sleep 2
( V ) (V) (V)(TERMINAL) -e “gdb -q -tui -x tools/gdbinit”

Terminal窗口此时停在0x0000fff0的位置,这是eip寄存器的值,而cs寄存器的值为0xf000. (遇到一个问题:此时无法正确反汇编出代码,使用x来查询内存0xfff0处的值时显示全0,不知道什么原因)

The target architecture is assumed to be i8086
0x0000fff0 in ?? ()
Breakpoint 1 at 0x7c00: file boot/bootasm.S, line 16.

输入si,执行1步,程序会跳转到0xe05b的地方。查看寄存器也可以发现eip的值变为0xe05b,而cs的值不变,仍然是0xf000.

反复输入si,以单步执行。(由于BIOS中全是汇编代码,看不懂其功能)。

问题2:在初始化位置0x7c00设置实地址断点,测试断点正常

我直接在tools/gdbinit中设置了断点break start,由于boot loader的入口为start,其地址为0x7c00,因此这和break *0x7c00效果是相同的。

设置断点后,输入continue或c,可以看到程序在0x7c00处停了下来,说明断点设置成功。

问题3:从0x7c00开始, 将反汇编代码与bootasm.S和bootblock.asm进行比较

反汇编的代码与bootblock.asm基本相同,而与bootasm.S的差别在于:
    反汇编的代码中的指令不带指示长度的后缀,而bootasm.S的指令则有。比如,反汇编 的代码是xor %eax, %eax,而bootasm.S的代码为xorw %ax, %ax
    反汇编的代码中的通用寄存器是32位(带有e前缀),而bootasm.S的代码中的通用寄存器是16位(不带e前缀)。

练习3

分析从bootloader进入保护模式的过程。BIOS 将通过读取硬盘主引导扇区到内存,并转跳到对应内存中的位置执行 bootloader。请分析bootloader是如何完成从实模式进入保护模式的

1、关闭中断,将各个段寄存器重置

它先将各个寄存器置0

cli               # Disable interrupts
cld               # String operations increment
xorw %ax, %ax     # Segment number zero
movw %ax, %ds     # -> Data Segment
movw %ax, %es     # -> Extra Segment
movw %ax, %ss     # -> Stack Segment

2、开启A20

然后就是将A20置1,这里简单解释一下A20,当 A20 地址线控制禁止时,则程序就像在 8086 中运行,1MB 以上的地是不可访问的。而在保护模式下 A20 地址线控制是要打开的,所以需要通过将键盘控制器上的A20线置于高电位,使得全部32条地址线可用。

seta20.1:
inb $0x64, %al # 读取状态寄存器,等待8042键盘控制器闲置
testb $0x2, %al # 判断输入缓存是否为空
jnz seta20.1

movb $0xd1, %al    # 0xd1表示写输出端口命令,参数随后通过0x60端口写入
outb %al, $0x64   

seta20.2:
inb $0x64, %al
testb $0x2, %al
jnz seta20.2

movb $0xdf, %al   # 通过0x60写入数据11011111 即将A20置1
outb %al, $0x60   

3、加载GDT表

lgdt gdtdesc

4、将CR0的第0位置1

movl %cr0, %eax
orl $CR0_PE_ON, %eax
movl %eax, %cr0

5、长跳转到32位代码段,重装CS和EIP

ljmp $PROT_MODE_CSEG, $protcseg

6、重装DS、ES等段寄存器等

movw $PROT_MODE_DSEG, %ax   # Our data segment selector
movw %ax, %ds     # -> DS: Data Segment
movw %ax, %es     # -> ES: Extra Segment
movw %ax, %fs     # -> FS
movw %ax, %gs     # -> GS
movw %ax, %ss     # -> SS: Stack Segment

7、转到保护模式完成,进入boot主方法

movl $0x0, %ebp
movl $start, %esp
call bootmain

练习4

分析bootloader加载ELF格式的OS的过程

  1. bootloader如何读取硬盘扇区的?
  2. bootloader是如何加载 ELF格式的 OS?

1.

在bootmain中,第一句就是读取硬盘扇区

readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0);

 static void
   readseg(uintptr_t va, uint32_t count, uint32_t offset) {
    uintptr_t end_va = va + count;
 
    // round down to sector boundary
    va -= offset % SECTSIZE;
 
    // translate from bytes to sectors; kernel starts at sector 1
    uint32_t secno = (offset / SECTSIZE) + 1;
 
    // If this is too slow, we could read lots of sectors at a time.
    // We'd write more to memory than asked, but it doesn't matter --
    // we load in increasing order.
    for (; va < end_va; va += SECTSIZE, secno ++) {
        readsect((void *)va, secno);
    }
    }
 
    readsect((void *)va, secno);
 
    static void
readsect(void *dst, uint32_t secno) {
    // wait for disk to be ready
    waitdisk();
 
    outb(0x1F2, 1);                         // count = 1
    outb(0x1F3, secno & 0xFF);
    outb(0x1F4, (secno >> 8) & 0xFF);
    outb(0x1F5, (secno >> 16) & 0xFF);
    outb(0x1F6, ((secno >> 24) & 0xF) | 0xE0);
    outb(0x1F7, 0x20);                      // cmd 0x20 - read sectors
 
    // wait for disk to be ready
    waitdisk();
 
    // read a sector
    insl(0x1F0, dst, SECTSIZE / 4);
}

大致流程如下:

(1).等待磁盘准备好
(2).发出读取扇区的命令
(3).等待磁盘准备好
(4).把磁盘扇区数据读取到指定内存

2.

bootmain(void) {
    //读取硬盘的第一个扇区
    readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0);
 
    // 判断是否是有效的ELF文件
    if (ELFHDR->e_magic != ELF_MAGIC) {
        goto bad;
    }
 
    struct proghdr *ph, *eph;
 
     //ELF头部有描述ELF文件应加载到内存什么位置的描述表,这里读取出来将之存入ph
    ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff);
    eph = ph + ELFHDR->e_phnum;
    //按照程序头表的描述,将ELF文件中的数据载入内存
    for (; ph < eph; ph ++) {
        readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset);
    }
 
    //根据ELF头表中的入口信息,找到内核的入口并开始运行 
    ((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))();

bad:
    outw(0x8A00, 0x8A00);
    outw(0x8A00, 0x8E00);
 
    while (1);
}

练习5

题目:
实现函数调用堆栈跟踪函数

先建立一个栈模型

ss:[ebp-8] ;变量2
ss:[ebp-4] ;变量1
ss:[ebp] ;栈针
ss:[ebp+4] ;返回地址
ss:[ebp+8] ;第一个参数

函数实现

read_ebp()和read_eip()函数来获取当前ebp寄存器和eip 寄存器的信息。

查看print_stackframe函数注释

/* 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 (unit32_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]
*/

for(i = 0; ebp!=0 && i < STACKFRAME_DEPTH; i++) {//STACKFRAME_DEPTH = 20 一直向上循环找到所有的调用函数为止,一开始没有判断栈针为空的条件
cprintf("ebp:0x%08x eip:0x%08x ",ebp, eip);
uint32_t *args = (uint32_t *)ebp + 2; //传参
for(j = 0; j < 4; j++)
cprintf(“0x%08x “,args[j]);
cprintf(”\n”);
print_debuginfo(eip-1);
//模拟函数执行完毕
eip = *((uint32_t *)ebp+1);//调用函数的返回地址
ebp = *((uint32_t *)ebp);//上一个函数的栈针
//循环直到没有调用函数停止
}

我们发现ebp的值在0x7bf8之后就为零了,这说明上面没有了调用函数,直接加个判断
ebp!=0 就可以输出预期的结果。

练习6

题目:
1.中断向量表中一个表项占多少字节?其中哪几位代表中断处理代码的入口?
2.请编程完善kern/trap/trap.c中对中断向量表进行初始化的函数idt_init。在idt_init函数中,依次对所有中断入口进行初始化。使用mmu.h中的SETGATE宏,填充idt数组内容。注意除了系统调用中断(T_SYSCALL)以外,其它中断均使用中断门描述符,权限为内核态权限;而系统调用中断使用异常,权限为陷阱门描述符。每个中断的入口由tools/vectors.c生成,使用trap.c中声明的vectors数组即可。
3.请编程完善trap.c中的中断处理函数trap在对时钟中断进行处理的部分填写trap函数中处理时钟中断的部分,使操作系统每遇到100次时钟中断后,调用 print_ticks子程序,向屏幕上打印一行文字100 ticks。

1.中断向量表中一个表项占多少字节?其中哪几位代表中断处理代码的入口?

中断向量表一个表项占用8字节,其中2-3字节是段选择子,0-1字节和6-7字节拼成位移,
两者联合便是中断处理程序的入口地址。
中断描述符表中一个表项占8个字节
其中015和4863分别为offset的低16位和高16位,16~31位是段选择子,通过段选择子得到段基址,再加上段内偏移量就可以得到中断处理代码的入口。

2.请编程完善kern/trap/trap.c中对中断向量表进行初始化的函数idt_init

这里这里主要就是实现对中断向量表的初始化。

重点就是两步
第一步,声明__vertors[],其中存放着中断服务程序的入口地址。这个数组生成于vertor.S中。
第二步,填充中断描述符表IDT。
第三部,加载中断描述符表。
对应到代码中如下所示:

void idt_init(void) {
extern uintptr_t __vectors[];//声明__vertors[]
int i;
for(i=0;i<256;i++) {
SETGATE(idt[i],0,GD_KTEXT,__vectors[i],DPL_KERNEL);
}
SETGATE(idt[T_SWITCH_TOK],0,GD_KTEXT,__vectors[T_SWITCH_TOK],DPL_USER);
lidt(&idt_pd);//使用lidt指令加载中断描述符表
}

3.请编程完善trap.c中的中断处理函数trap在对时钟中断进行处理的部分填写trap函数中处理时钟中断的部分,使操作系统每遇到100次时钟中断后,调用 print_ticks子程序,向屏幕上打印一行文字100 ticks

根据指导书查看函数trap_dispatch,发现print_ticks()子程序已经被实现了,所以直接进行判断输出即可,如下:



case IRQ_OFFSET + IRQ_TIMER:
ticks ++; //每一次时钟信号会使变量ticks加1
if (ticks==TICK_NUM) {//TICK_NUM已经被预定义成了100,每到100便调用print_ticks()函数打印
ticks-=TICK_NUM;
print_ticks();
}
break;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值