基于ucore的操作系统实验lab1
程序员文章站
2022-06-19 16:42:03
...
基于ucore的操作系统实验lab1
[练习1]
练习1.1
1、先编译链接生成kernel文件
2、再编译链接生成bootblock文件
3、生成ucore.imgsign.c生成bootblock
1.2 查看sign.c可以发现关键在于bootblock.out小于510bytes,且 buf[510] = 0x55; buf[511] = 0xAA;
[练习2]
练习2.1 从 CPU 加电后执行的第一条指令开始,单步跟踪 BIOS 的执行。
单步跟踪,方法如下:
1 修改 lab1/tools/gdbinit,内容为:
set architecture
i8086
target remote
:1234
2 在 lab1目录下,执行
make debug
3 在看到gdb的调试界面(gdb)后,在gdb调试界面下执行如下命令
si
即可单步跟踪BIOS了。
4 在gdb界面下,可通过如下命令来看BIOS的代码
x /2i $pc
//显示当前eip处的汇编指令
练习2.2 在初始化位置0x7c00设置实地址断点,测试断点正常。
在gdb中输入一下命令
b *0x7c00
//在0x7c00处设置断点。此地址是bootloader入口点地址,可看boot/bootasm.S的start地址处
c
//continue简称,表示继续执行
x /2i $pc
//显示当前eip处的汇编指令
得到
Breakpoint 2,
0x00007c00 in ?? ()
=> 0x7c00: cli
0x7c01: cld
练习2.3 从0x7c00开始跟踪代码运行,将单步跟踪反汇编得到的代码与bootasm.S和 bootblock.asm进行比较。
将2.2中的步骤重复实现与两个文件进行对比发现是一致的
练习2.4 自己找一个bootloader或内核中的代码位置,设置断点并进行测试。
自己按以上步骤测试一下就行了
练习3
练习3.1 分析bootloader进入保护模式的过程。
它先将各个寄存器置0
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
初始化GDT表:一个简单的GDT表和其描述符已经静态储存在引导区中,载入即可
lgdt gdtdesc
进入保护模式:通过将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
转到保护模式完成,进入boot主方法
call bootmain
练习4 分析bootloader加载ELF格式的OS的过程。
首先看readsect函数,
readsect
从设备的第secno扇区读取数据到dst位置
static
void
readsect(void
*dst, uint32_t secno) {
waitdisk();
outb(0x1F2, 1); // 设置读取扇区的数目为1
outb(0x1F3, secno & 0xFF);
outb(0x1F4, (secno >> 8) & 0xFF);
outb(0x1F5, (secno >> 16) &
0xFF);
outb(0x1F6, ((secno >> 24) & 0xF)
| 0xE0);
// 上面四条指令联合制定了扇区号
// 在这4个字节线联合构成的32位参数中
//
29-31位强制设为1
//
28位(=0)表示访问"Disk
0"
//
0-27位是28位的偏移量
outb(0x1F7, 0x20); // 0x20命令,读取扇区
waitdisk();
insl(0x1F0, dst, SECTSIZE / 4); // 读取到dst位置,
// 幻数4因为这里以DW为单位
}
readseg简单包装了readsect,可以从设备读取任意长度的内容。
static
void
readseg(uintptr_t
va, uint32_t count, uint32_t offset) {
uintptr_t end_va = va + count;
va -= offset % SECTSIZE;
uint32_t secno = (offset / SECTSIZE) + 1;
// 加1因为0扇区被引导占用
// ELF文件从1扇区开始
for (; va < end_va; va += SECTSIZE,
secno ++) {
readsect((void *)va, secno);
}
}
在bootmain函数中,
void
bootmain(void)
{
// 首先读取ELF的头部
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文件0x1000位置后面的0xd1ec比特被载入内存0x00100000
// ELF文件0xf000位置后面的0x1d20比特被载入内存0x0010e000
// 根据ELF头部储存的入口信息,找到内核的入口
((void (*)(void))(ELFHDR->e_entry &
0xFFFFFF))();
bad:
outw(0x8A00, 0x8A00);
outw(0x8A00, 0x8E00);
while (1);
}
练习5
实现函数调用堆栈跟踪函数
ss:ebp指向的堆栈位置储存着caller的ebp,以此为线索可以得到所有使用堆栈的函数ebp。
ss:ebp+4指向caller调用时的eip,ss:ebp+8等是(可能的)参数。
输出中,堆栈最深一层为
ebp:0x00007bf8
eip:0x00007d68 \
args:0x00000000
0x00000000 0x00000000 0x00007c4f
<unknow>: -- 0x00007d67 --
下一篇: JS页面跳转代码