update lab1 report
This commit is contained in:
parent
2ed35af13e
commit
8cf7875006
@ -1,9 +1,11 @@
|
|||||||
Lab1 report
|
= Lab1 report =
|
||||||
[练习1]
|
|
||||||
|
== [练习1] ==
|
||||||
|
|
||||||
[练习1.1] 操作系统镜像文件 ucore.img 是如何一步一步生成的?(需要比较详细地解释 Makefile 中
|
[练习1.1] 操作系统镜像文件 ucore.img 是如何一步一步生成的?(需要比较详细地解释 Makefile 中
|
||||||
每一条相关命令和命令参数的含义,以及说明命令导致的结果)
|
每一条相关命令和命令参数的含义,以及说明命令导致的结果)
|
||||||
|
|
||||||
|
```
|
||||||
bin/ucore.img
|
bin/ucore.img
|
||||||
| 生成ucore.img的相关代码为
|
| 生成ucore.img的相关代码为
|
||||||
| $(UCOREIMG): $(kernel) $(bootblock)
|
| $(UCOREIMG): $(kernel) $(bootblock)
|
||||||
@ -137,6 +139,7 @@ bin/ucore.img
|
|||||||
|
|
|
|
||||||
| 从第二个块开始写kernel中的内容
|
| 从第二个块开始写kernel中的内容
|
||||||
| dd if=bin/kernel of=bin/ucore.img seek=1 conv=notrunc
|
| dd if=bin/kernel of=bin/ucore.img seek=1 conv=notrunc
|
||||||
|
```
|
||||||
|
|
||||||
[练习1.2] 一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?
|
[练习1.2] 一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?
|
||||||
|
|
||||||
@ -146,28 +149,37 @@ bin/ucore.img
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
[练习2]
|
== [练习2] ==
|
||||||
|
|
||||||
[练习2.1] 从 CPU 加电后执行的第一条指令开始,单步跟踪 BIOS 的执行。
|
[练习2.1] 从 CPU 加电后执行的第一条指令开始,单步跟踪 BIOS 的执行。
|
||||||
|
|
||||||
通过改写Makefile文件 ()
|
通过改写Makefile文件
|
||||||
|
|
||||||
|
```
|
||||||
debug: $(UCOREIMG)
|
debug: $(UCOREIMG)
|
||||||
$(V)$(TERMINAL) -e "$(QEMU) -S -s -d in_asm -D $(BINDIR)/q.log -parallel stdio -hda $< -serial null"
|
$(V)$(TERMINAL) -e "$(QEMU) -S -s -d in_asm -D $(BINDIR)/q.log -parallel stdio -hda $< -serial null"
|
||||||
$(V)sleep 2
|
$(V)sleep 2
|
||||||
$(V)$(TERMINAL) -e "gdb -q -tui -x tools/gdbinit"
|
$(V)$(TERMINAL) -e "gdb -q -tui -x tools/gdbinit"
|
||||||
在调用qemu时增加-d in_asm -D q.log参数,便可以将运行的汇编指令保存在q.log中。
|
```
|
||||||
为防止qemu在gdb连接后立即开始执行,删除了tools/gdbinit中的"continue"行。
|
|
||||||
|
在调用qemu时增加`-d in_asm -D q.log`参数,便可以将运行的汇编指令保存在q.log中。
|
||||||
|
为防止qemu在gdb连接后立即开始执行,删除了`tools/gdbinit`中的`continue`行。
|
||||||
|
|
||||||
[练习2.2] 在初始化位置0x7c00 设置实地址断点,测试断点正常。
|
[练习2.2] 在初始化位置0x7c00 设置实地址断点,测试断点正常。
|
||||||
|
|
||||||
在tools/gdbinit结尾加上
|
在tools/gdbinit结尾加上
|
||||||
|
|
||||||
|
```
|
||||||
set architecture i8086 //设置当前调试的CPU是8086
|
set architecture i8086 //设置当前调试的CPU是8086
|
||||||
b *0x7c00 //在0x7c00处设置断点。此地址是bootloader入口点地址,可看boot/bootasm.S的start地址处
|
b *0x7c00 //在0x7c00处设置断点。此地址是bootloader入口点地址,可看boot/bootasm.S的start地址处
|
||||||
c //continue简称,表示继续执行
|
c //continue简称,表示继续执行
|
||||||
x /2i $pc //显示当前eip处的汇编指令
|
x /2i $pc //显示当前eip处的汇编指令
|
||||||
set architecture i386 //设置当前调试的CPU是80386
|
set architecture i386 //设置当前调试的CPU是80386
|
||||||
|
```
|
||||||
|
|
||||||
运行"make debug"便可得到
|
运行"make debug"便可得到
|
||||||
|
|
||||||
|
```
|
||||||
Breakpoint 2, 0x00007c00 in ?? ()
|
Breakpoint 2, 0x00007c00 in ?? ()
|
||||||
=> 0x7c00: cli
|
=> 0x7c00: cli
|
||||||
0x7c01: cld
|
0x7c01: cld
|
||||||
@ -179,15 +191,20 @@ bin/ucore.img
|
|||||||
0x7c0c: test $0x2,%al
|
0x7c0c: test $0x2,%al
|
||||||
0x7c0e: jne 0x7c0a
|
0x7c0e: jne 0x7c0a
|
||||||
0x7c10: mov $0xd1,%al
|
0x7c10: mov $0xd1,%al
|
||||||
|
```
|
||||||
|
|
||||||
[练习2.3] 在调用qemu 时增加-d in_asm -D q.log 参数,便可以将运行的汇编指令保存在q.log 中。
|
[练习2.3] 在调用qemu 时增加-d in_asm -D q.log 参数,便可以将运行的汇编指令保存在q.log 中。
|
||||||
将执行的汇编代码与bootasm.S 和 bootblock.asm 进行比较,看看二者是否一致。
|
将执行的汇编代码与bootasm.S 和 bootblock.asm 进行比较,看看二者是否一致。
|
||||||
|
|
||||||
在tools/gdbinit结尾加上
|
在tools/gdbinit结尾加上
|
||||||
|
```
|
||||||
b *0x7c00
|
b *0x7c00
|
||||||
c
|
c
|
||||||
x /10i $pc
|
x /10i $pc
|
||||||
|
```
|
||||||
|
|
||||||
便可以在q.log中读到"call bootmain"前执行的命令
|
便可以在q.log中读到"call bootmain"前执行的命令
|
||||||
|
```
|
||||||
----------------
|
----------------
|
||||||
IN:
|
IN:
|
||||||
0x00007c00: cli
|
0x00007c00: cli
|
||||||
@ -257,13 +274,17 @@ bin/ucore.img
|
|||||||
----------------
|
----------------
|
||||||
IN:
|
IN:
|
||||||
0x00007d0d: push %ebp
|
0x00007d0d: push %ebp
|
||||||
|
```
|
||||||
|
|
||||||
其与bootasm.S和bootblock.asm中的代码相同。
|
其与bootasm.S和bootblock.asm中的代码相同。
|
||||||
|
|
||||||
[练习3] 分析bootloader 进入保护模式的过程。
|
== [练习3] ==
|
||||||
|
分析bootloader 进入保护模式的过程。
|
||||||
|
|
||||||
从%cs=0 $pc=0x7c00,进入后
|
从`%cs=0 $pc=0x7c00`,进入后
|
||||||
|
|
||||||
首先清理环境:包括将flag置0和将段寄存器置0
|
首先清理环境:包括将flag置0和将段寄存器置0
|
||||||
|
```
|
||||||
.code16
|
.code16
|
||||||
cli
|
cli
|
||||||
cld
|
cld
|
||||||
@ -271,9 +292,11 @@ bin/ucore.img
|
|||||||
movw %ax, %ds
|
movw %ax, %ds
|
||||||
movw %ax, %es
|
movw %ax, %es
|
||||||
movw %ax, %ss
|
movw %ax, %ss
|
||||||
|
```
|
||||||
|
|
||||||
开启A20:通过将键盘控制器上的A20线置于高电位,全部32条地址线可用,
|
开启A20:通过将键盘控制器上的A20线置于高电位,全部32条地址线可用,
|
||||||
可以访问4G的内存空间。
|
可以访问4G的内存空间。
|
||||||
|
```
|
||||||
seta20.1: # 等待8042键盘控制器不忙
|
seta20.1: # 等待8042键盘控制器不忙
|
||||||
inb $0x64, %al #
|
inb $0x64, %al #
|
||||||
testb $0x2, %al #
|
testb $0x2, %al #
|
||||||
@ -289,21 +312,29 @@ bin/ucore.img
|
|||||||
|
|
||||||
movb $0xdf, %al # 打开A20
|
movb $0xdf, %al # 打开A20
|
||||||
outb %al, $0x60 #
|
outb %al, $0x60 #
|
||||||
|
```
|
||||||
|
|
||||||
初始化GDT表:一个简单的GDT表和其描述符已经静态储存在引导区中,载入即可
|
初始化GDT表:一个简单的GDT表和其描述符已经静态储存在引导区中,载入即可
|
||||||
|
```
|
||||||
lgdt gdtdesc
|
lgdt gdtdesc
|
||||||
|
```
|
||||||
|
|
||||||
进入保护模式:通过将cr0寄存器PE位置1便开启了保护模式
|
进入保护模式:通过将cr0寄存器PE位置1便开启了保护模式
|
||||||
|
```
|
||||||
movl %cr0, %eax
|
movl %cr0, %eax
|
||||||
orl $CR0_PE_ON, %eax
|
orl $CR0_PE_ON, %eax
|
||||||
movl %eax, %cr0
|
movl %eax, %cr0
|
||||||
|
```
|
||||||
|
|
||||||
通过长跳转更新cs的基地址
|
通过长跳转更新cs的基地址
|
||||||
|
```
|
||||||
ljmp $PROT_MODE_CSEG, $protcseg
|
ljmp $PROT_MODE_CSEG, $protcseg
|
||||||
.code32
|
.code32
|
||||||
protcseg:
|
protcseg:
|
||||||
|
```
|
||||||
|
|
||||||
设置段寄存器,并建立堆栈
|
设置段寄存器,并建立堆栈
|
||||||
|
```
|
||||||
movw $PROT_MODE_DSEG, %ax
|
movw $PROT_MODE_DSEG, %ax
|
||||||
movw %ax, %ds
|
movw %ax, %ds
|
||||||
movw %ax, %es
|
movw %ax, %es
|
||||||
@ -312,16 +343,19 @@ bin/ucore.img
|
|||||||
movw %ax, %ss
|
movw %ax, %ss
|
||||||
movl $0x0, %ebp
|
movl $0x0, %ebp
|
||||||
movl $start, %esp
|
movl $start, %esp
|
||||||
|
```
|
||||||
转到保护模式完成,进入boot主方法
|
转到保护模式完成,进入boot主方法
|
||||||
|
```
|
||||||
call bootmain
|
call bootmain
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
== [练习4] ==
|
||||||
[练习4] :分析bootloader加载ELF格式的OS的过程。
|
分析bootloader加载ELF格式的OS的过程。
|
||||||
|
|
||||||
首先看readsect函数,
|
首先看readsect函数,
|
||||||
readsect从设备的第secno扇区读取数据到dst位置
|
`readsect`从设备的第secno扇区读取数据到dst位置
|
||||||
|
```
|
||||||
static void
|
static void
|
||||||
readsect(void *dst, uint32_t secno) {
|
readsect(void *dst, uint32_t secno) {
|
||||||
waitdisk();
|
waitdisk();
|
||||||
@ -343,8 +377,10 @@ readsect从设备的第secno扇区读取数据到dst位置
|
|||||||
insl(0x1F0, dst, SECTSIZE / 4); // 读取到dst位置,
|
insl(0x1F0, dst, SECTSIZE / 4); // 读取到dst位置,
|
||||||
// 幻数4因为这里以DW为单位
|
// 幻数4因为这里以DW为单位
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
readseg简单包装了readsect,可以从设备读取任意长度的内容。
|
readseg简单包装了readsect,可以从设备读取任意长度的内容。
|
||||||
|
```
|
||||||
static void
|
static void
|
||||||
readseg(uintptr_t va, uint32_t count, uint32_t offset) {
|
readseg(uintptr_t va, uint32_t count, uint32_t offset) {
|
||||||
uintptr_t end_va = va + count;
|
uintptr_t end_va = va + count;
|
||||||
@ -359,9 +395,10 @@ readseg简单包装了readsect,可以从设备读取任意长度的内容。
|
|||||||
readsect((void *)va, secno);
|
readsect((void *)va, secno);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
在bootmain函数中,
|
在bootmain函数中,
|
||||||
|
```
|
||||||
void
|
void
|
||||||
bootmain(void) {
|
bootmain(void) {
|
||||||
// 首先读取ELF的头部
|
// 首先读取ELF的头部
|
||||||
@ -394,7 +431,7 @@ readseg简单包装了readsect,可以从设备读取任意长度的内容。
|
|||||||
outw(0x8A00, 0x8E00);
|
outw(0x8A00, 0x8E00);
|
||||||
while (1);
|
while (1);
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
[练习5] 实现函数调用堆栈跟踪函数
|
[练习5] 实现函数调用堆栈跟踪函数
|
||||||
@ -403,16 +440,20 @@ ss:ebp指向的堆栈位置储存着caller的ebp,以此为线索可以得到
|
|||||||
ss:ebp+4指向caller调用时的eip,ss:ebp+8等是(可能的)参数。
|
ss:ebp+4指向caller调用时的eip,ss:ebp+8等是(可能的)参数。
|
||||||
|
|
||||||
输出中,堆栈最深一层为
|
输出中,堆栈最深一层为
|
||||||
|
```
|
||||||
ebp:0x00007bf8 eip:0x00007d68 \
|
ebp:0x00007bf8 eip:0x00007d68 \
|
||||||
args:0x00000000 0x00000000 0x00000000 0x00007c4f
|
args:0x00000000 0x00000000 0x00000000 0x00007c4f
|
||||||
<unknow>: -- 0x00007d67 --
|
<unknow>: -- 0x00007d67 --
|
||||||
|
```
|
||||||
|
|
||||||
其对应的是第一个使用堆栈的函数,bootmain.c中的bootmain。
|
其对应的是第一个使用堆栈的函数,bootmain.c中的bootmain。
|
||||||
bootloader设置的堆栈从0x7c00开始,使用"call bootmain"转入bootmain函数。
|
bootloader设置的堆栈从0x7c00开始,使用"call bootmain"转入bootmain函数。
|
||||||
call指令压栈,所以bootmain中ebp为0x7bf8。
|
call指令压栈,所以bootmain中ebp为0x7bf8。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[练习6] 完善中断初始化和处理
|
== [练习6] ==
|
||||||
|
完善中断初始化和处理
|
||||||
|
|
||||||
[练习6.1] 中断向量表中一个表项占多少字节?其中哪几位代表中断处理代码的入口?
|
[练习6.1] 中断向量表中一个表项占多少字节?其中哪几位代表中断处理代码的入口?
|
||||||
|
|
||||||
@ -420,13 +461,17 @@ call指令压栈,所以bootmain中ebp为0x7bf8。
|
|||||||
两者联合便是中断处理程序的入口地址。
|
两者联合便是中断处理程序的入口地址。
|
||||||
|
|
||||||
[练习6.2] 请编程完善kern/trap/trap.c中对中断向量表进行初始化的函数idt_init。
|
[练习6.2] 请编程完善kern/trap/trap.c中对中断向量表进行初始化的函数idt_init。
|
||||||
|
|
||||||
见代码
|
见代码
|
||||||
|
|
||||||
[练习6.3] 请编程完善trap.c中的中断处理函数trap,在对时钟中断进行处理的部分填写trap函数
|
[练习6.3] 请编程完善trap.c中的中断处理函数trap,在对时钟中断进行处理的部分填写trap函数
|
||||||
|
|
||||||
见代码
|
见代码
|
||||||
|
|
||||||
|
|
||||||
[练习7] 增加syscall功能,即增加一用户态函数(可执行一特定系统调用:获得时钟计数值),
|
== [练习7] ==
|
||||||
|
|
||||||
|
增加syscall功能,即增加一用户态函数(可执行一特定系统调用:获得时钟计数值),
|
||||||
当内核初始完毕后,可从内核态返回到用户态的函数,而用户态的函数又通过系统调用得到内核态的服务
|
当内核初始完毕后,可从内核态返回到用户态的函数,而用户态的函数又通过系统调用得到内核态的服务
|
||||||
|
|
||||||
在idt_init中,将用户态调用SWITCH_TOK中断的权限打开。
|
在idt_init中,将用户态调用SWITCH_TOK中断的权限打开。
|
||||||
@ -434,18 +479,24 @@ call指令压栈,所以bootmain中ebp为0x7bf8。
|
|||||||
|
|
||||||
在trap_dispatch中,将iret时会从堆栈弹出的段寄存器进行修改
|
在trap_dispatch中,将iret时会从堆栈弹出的段寄存器进行修改
|
||||||
对TO User
|
对TO User
|
||||||
|
```
|
||||||
tf->tf_cs = USER_CS;
|
tf->tf_cs = USER_CS;
|
||||||
tf->tf_ds = USER_DS;
|
tf->tf_ds = USER_DS;
|
||||||
tf->tf_es = USER_DS;
|
tf->tf_es = USER_DS;
|
||||||
tf->tf_ss = USER_DS;
|
tf->tf_ss = USER_DS;
|
||||||
|
```
|
||||||
对TO Kernel
|
对TO Kernel
|
||||||
|
|
||||||
|
```
|
||||||
tf->tf_cs = KERNEL_CS;
|
tf->tf_cs = KERNEL_CS;
|
||||||
tf->tf_ds = KERNEL_DS;
|
tf->tf_ds = KERNEL_DS;
|
||||||
tf->tf_es = KERNEL_DS;
|
tf->tf_es = KERNEL_DS;
|
||||||
|
```
|
||||||
|
|
||||||
在lab1_switch_to_user中,调用T_SWITCH_TOU中断。
|
在lab1_switch_to_user中,调用T_SWITCH_TOU中断。
|
||||||
注意从中断返回时,会多pop两位,并用这两位的值更新ss,sp,损坏堆栈。
|
注意从中断返回时,会多pop两位,并用这两位的值更新ss,sp,损坏堆栈。
|
||||||
所以要先把栈压两位,并在从中断返回后修复esp。
|
所以要先把栈压两位,并在从中断返回后修复esp。
|
||||||
|
```
|
||||||
asm volatile (
|
asm volatile (
|
||||||
"sub $0x8, %%esp \n"
|
"sub $0x8, %%esp \n"
|
||||||
"int %0 \n"
|
"int %0 \n"
|
||||||
@ -453,17 +504,21 @@ call指令压栈,所以bootmain中ebp为0x7bf8。
|
|||||||
:
|
:
|
||||||
: "i"(T_SWITCH_TOU)
|
: "i"(T_SWITCH_TOU)
|
||||||
);
|
);
|
||||||
|
```
|
||||||
|
|
||||||
在lab1_switch_to_kernel中,调用T_SWITCH_TOK中断。
|
在lab1_switch_to_kernel中,调用T_SWITCH_TOK中断。
|
||||||
注意从中断返回时,esp仍在TSS指示的堆栈中。所以要在从中断返回后修复esp。
|
注意从中断返回时,esp仍在TSS指示的堆栈中。所以要在从中断返回后修复esp。
|
||||||
|
```
|
||||||
asm volatile (
|
asm volatile (
|
||||||
"int %0 \n"
|
"int %0 \n"
|
||||||
"movl %%ebp, %%esp \n"
|
"movl %%ebp, %%esp \n"
|
||||||
:
|
:
|
||||||
: "i"(T_SWITCH_TOK)
|
: "i"(T_SWITCH_TOK)
|
||||||
);
|
);
|
||||||
|
```
|
||||||
|
|
||||||
但这样不能正常输出文本。根据提示,在trap_dispatch中转User态时,将调用io所需权限降低。
|
但这样不能正常输出文本。根据提示,在trap_dispatch中转User态时,将调用io所需权限降低。
|
||||||
|
```
|
||||||
tf->tf_eflags |= 0x3000;
|
tf->tf_eflags |= 0x3000;
|
||||||
|
```
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user