查看Makefile中的相关代码如下:
-生成kernel的相关代码:
KINCLUDE += kern/debug/ \
kern/driver/ \
kern/trap/ \
kern/mm/
KSRCDIR += kern/init \
kern/libs \
kern/debug \
kern/driver \
kern/trap \
kern/mm
KCFLAGS += $(addprefix -I,$(KINCLUDE))
$(call add_files_cc,$(call listf_cc,$(KSRCDIR)),kernel,$(KCFLAGS)) //首先生成相关文件,如init.o等
KOBJS = $(call read_packet,kernel libs)
# create kernel target
kernel = $(call totarget,kernel)
$(kernel): tools/kernel.ld //相关文件备齐,开始生成kernel
$(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)
copy
-生成bootblock的相关代码:
bootfiles = $(call listf_cc,boot) //生成bootasm.o,bootmain.o等
$(foreach f,$(bootfiles),$(call cc_compile,$(f),$(CC),$(CFLAGS) -Os -nostdinc))
bootblock = $(call totarget,bootblock)
//根据bootasm.o,bootmain.o以及sign生成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)
# -------------------------------------------------------------------
# create 'sign' tools
$(call add_files_host,tools/sign.c,sign,sign) //生成sign
$(call create_target_host,sign,sign)
copy
-生成ucore.img的相关代码:
UCOREIMG := $(call totarget,ucore.img)
$(UCOREIMG): $(kernel) $(bootblock)
$(V)dd if=/dev/zero of=$@ count=10000 //生成10000个块的文件,每块512字节,用0填充
$(V)dd if=$(bootblock) of=$@ conv=notrunc //向第一个块中写入bootblock的内容
$(V)dd if=$(kernel) of=$@ seek=1 conv=notrunc //从第二个块开始写入kernel内容
$(call create_target,ucore.img)
copy
通过make "V=",可以看到各部分代码对应执行的实际内容如下:
1.1生成kernel相关文件对应的命令为(以init.o为例):
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
copy
1.2生成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
+ cc boot/bootasm.S
copy
2.1生成bootasm.o的实际命令为:
gcc -Iboot/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Os -nostdinc -c boot/bootasm.S -o obj/boot/bootasm.o
copy
2.2生成bootmain.o的实际命令为:
gcc -Iboot/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Os -nostdinc -c boot/bootmain.c -o obj/boot/bootmain.o
copy
2.3生成sign的命令为:
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
copy
2.4生成bootblock的相关部分为:
ld -m elf_i386 -nostdlib -N -e start -Ttext 0x7C00 obj/boot/bootasm.o obj/boot/bootmain.o -o obj/bootblock.o
'obj/bootblock.out' size: 472 bytes
build 512 bytes boot sector: 'bin/bootblock' success!
copy
3.生成ucore.img的相关部分为:
dd if=/dev/zero of=bin/ucore.img count=10000
10000+0 records in
10000+0 records out
5120000 bytes (5.1 MB) copied, 0.0311037 s, 165 MB/s
dd if=bin/bootblock of=bin/ucore.img conv=notrunc
1+0 records in
1+0 records out
512 bytes (512 B) copied, 7.0769e-05 s, 7.2 MB/s
dd if=bin/kernel of=bin/ucore.img seek=1 conv=notrunc
146+1 records in
146+1 records out
74871 bytes (75 kB) copied, 0.00390632 s, 19.2 MB/s
copy
sign.c的代码主要部分如下:
char buf[512];
memset(buf, 0, sizeof(buf));
FILE *ifp = fopen(argv[1], "rb");
int size = fread(buf, 1, st.st_size, ifp);
if (size != st.st_size) {
fprintf(stderr, "read '%s' error, size is %d.\n", argv[1], size);
return -1;
}
fclose(ifp);
buf[510] = 0x55;
buf[511] = 0xAA;
FILE *ofp = fopen(argv[2], "wb+");
size = fwrite(buf, 1, 512, ofp);
if (size != 512) {
fprintf(stderr, "write '%s' error, size is %d.\n", argv[2], size);
return -1;
}
fclose(ofp);
printf("build 512 bytes boot sector: '%s' success!\n", argv[2]);
copy
从代码可以看到,主引导扇区大小固定为512字节,且最后两个字节分别为0x55和0xAA。
将tools/gdbinit中内容修改为:
set architecture i8086
target remote :1234
copy
运行"make debug"后,可在gdb界面看到加电后第一条指令位置为0xffff0,相应可通过x /i 0xffff0查看到对应命令为:
0xffff0: ljmp $0xf000,$0xe05b
copy
后续可通过si命令单步跟踪BIOS执行,并通过上述方法查看对应命令。
在原Makefile文件中增加如下部分:
lab1-mon: $(UCOREIMG)
$(V)$(TERMINAL) -e "$(QEMU) -S -s -d in_asm -D $(BINDIR)/q.log -monitor stdio -hda $< -serial null"
$(V)sleep 2
$(V)$(TERMINAL) -e "gdb -q -x tools/lab1init"
copy
对应的lab1init内容为:
target remote:1234 //连接qemu
set architecture i8086 //设置当前CPU为8086,对应16位实模式
b *0x7c00 //在0x7c00处设置断点。
continue //继续执行
x /2i $pc //显示当前eip处的汇编指令
copy
运行"make lab1-mon"便可看到断点正常,输出如下:
Breakpoint 1, 0x00007c00 in ?? ()
=> 0x7c00: cli
0x7c01: cld
copy
在[练习2.2]中已在lab1-mon中添加相关参数,打开q.log可查到从0x00007c00开始的汇编指令如下:
IN:
0x00007c00: cli
----------------
IN:
0x00007c00: cli
0x00007c01: cld
0x00007c02: xor %ax,%ax
0x00007c04: mov %ax,%ds
0x00007c06: mov %ax,%es
0x00007c08: mov %ax,%ss
----------------
IN:
0x00007c0a: in $0x64,%al
----------------
IN:
0x00007c0c: test $0x2,%al
0x00007c0e: jne 0x7c0a
----------------
IN:
0x00007c10: mov $0xd1,%al
0x00007c12: out %al,$0x64
0x00007c14: in $0x64,%al
0x00007c16: test $0x2,%al
0x00007c18: jne 0x7c14
----------------
IN:
0x00007c1a: mov $0xdf,%al
0x00007c1c: out %al,$0x60
0x00007c1e: lgdtw 0x7c6c
0x00007c23: mov %cr0,%eax
0x00007c26: or $0x1,%eax
0x00007c2a: mov %eax,%cr0
----------------
IN:
0x00007c2d: ljmp $0x8,$0x7c32
----------------
IN:
0x00007c32: mov $0x10,%ax
0x00007c36: mov %eax,%ds
----------------
IN:
0x00007c38: mov %eax,%es
----------------
IN:
0x00007c3a: mov %eax,%fs
0x00007c3c: mov %eax,%gs
0x00007c3e: mov %eax,%ss
----------------
IN:
0x00007c40: mov $0x0,%ebp
----------------
IN:
0x00007c45: mov $0x7c00,%esp
0x00007c4a: call 0x7cd1
copy
经比较其与bootasm.S和bootblock.asm中的代码相同。
bootasm.S中相关代码如下:
cli // 关中断
cld // Flag寄存器方向标志位DF置零
// 段寄存器置零
xorw %ax, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %ss
// 开启A20
seta20.1:
inb $0x64, %al // 等待8042 input buffer为空
testb $0x2, %al
jnz seta20.1
movb $0xd1, %al // 发送0xd1至8042的0x64端口,意为写入8042的P2 port的命令
outb %al, $0x64
seta20.2:
inb $0x64, %al // 等待8042 input buffer为空
testb $0x2, %al
jnz seta20.2
movb $0xdf, %al // 发送0xdf=11011111至8042的0x60端口,意为将8042的P2 port的A20设置位置1,即开启A20
outb %al, $0x60
lgdt gdtdesc // 载入以初始化GDT表
//将cr0寄存器PE位置1,开启保护模式
movl %cr0, %eax
orl $CR0_PE_ON, %eax
movl %eax, %cr0
// 长跳转进入32位模式
ljmp $PROT_MODE_CSEG, $protcseg
copy
bootmain.c中相关代码如下:
// 读取单扇区函数
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);
}
// 调用读取单扇区函数,实现读取段数据函数
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);
}
}
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头设置相关程序头指针,依次读取段数据至内存
ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff);
eph = ph + ELFHDR->e_phnum;
for (; ph < eph; ph ++) {
readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset);
}
// ELF格式的OS加载完成,根据ELF头的相关信息,转入kernel入口
((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))();
bad:
outw(0x8A00, 0x8A00);
outw(0x8A00, 0x8E00);
/* do nothing */
while (1);
}
copy
修改kdebug.c中的print_stackframe函数代码如下:
void
print_stackframe(void) {
uint32_t ebp = read_ebp(),eip = read_eip();
int i,j;
for(i = 0;(ebp!=0) && (i < STACKFRAME_DEPTH);i ++){
cprintf("ebp : 0x%08x,eip : 0x%08x args:",ebp,eip);
uint32_t *args = (uint32_t *)ebp + 2;
for(j = 0;j < 4;j ++){
cprintf("0x%08x ",j,*(args + j));
}
cprintf("\n");
print_debuginfo(eip-1);
eip = *((uint32_t *)ebp + 1);
ebp = *((uint32_t *) ebp);
}
copy
执行"make qemu"命令后对应输出如下:
ebp : 0x00007b08,eip : 0x001009a6 args:0x00000000 0x00000001 0x00000002 0x00000003
kern/debug/kdebug.c:294: print_stackframe+21
ebp : 0x00007b18,eip : 0x00100c9c args:0x00000000 0x00000001 0x00000002 0x00000003
kern/debug/kmonitor.c:125: mon_backtrace+10
ebp : 0x00007b38,eip : 0x00100092 args:0x00000000 0x00000001 0x00000002 0x00000003
kern/init/init.c:48: grade_backtrace2+33
ebp : 0x00007b58,eip : 0x001000bb args:0x00000000 0x00000001 0x00000002 0x00000003
kern/init/init.c:53: grade_backtrace1+38
ebp : 0x00007b78,eip : 0x001000d9 args:0x00000000 0x00000001 0x00000002 0x00000003
kern/init/init.c:58: grade_backtrace0+23
ebp : 0x00007b98,eip : 0x001000fe args:0x00000000 0x00000001 0x00000002 0x00000003
kern/init/init.c:63: grade_backtrace+34
ebp : 0x00007bc8,eip : 0x00100055 args:0x00000000 0x00000001 0x00000002 0x00000003
kern/init/init.c:28: kern_init+84
ebp : 0x00007bf8,eip : 0x00007d68 args:0x00000000 0x00000001 0x00000002 0x00000003
<unknow>: -- 0x00007d67 --
copy
与实验要求基本一致。其中最后一行为:
ebp : 0x00007bf8,eip : 0x00007d68 args:0x00000000 0x00000001 0x00000002 0x00000003
<unknow>: -- 0x00007d67 --
copy
bootblock中对应的汇编命令如下:
0x00007c40: mov $0x0,%ebp
0x00007c45: mov $0x7c00,%esp
0x00007c4a: call 0x7cd1
copy
初始esp设为0x7c00,下一条call指令压栈后,对应的ebp即变为0x7bf8,与实验输出的最后一行相同。因此该位置即对应bootsam.S中的call bootmain,即bootmain函数。
中断向量表一个表项占用8个字节,其中第0至15位表示offset的低16位,第48至63位表示offset的高16位。第16至31位表示段选择子。两者结合用于表示中断处理代码的入口地址。
idt_init函数代码如下:
void
idt_init(void) {
extern uintptr_t __vectors[];
int i;
for(i=0;i<sizeof(idt)/sizeof(struct gatedesc);i++){
SETGATE(idt[i],0,GD_KTEXT,__vectors[i],DPL_KERNEL); //调用SETGATE宏和__vectors[],依序设置IDT各项
}
SETGATE(idt[T_SWITCH_TOK],0,GD_KTEXT,__vectors[T_SWITCH_TOK],DPL_USER); //设置用户态切换到内核态对应表项
lidt(&idt_pd);
}
copy
trap函数中时钟中断部分代码如下:
case IRQ_OFFSET + IRQ_TIMER:
ticks++; //每次全局变量ticks+1
if(ticks%TICK_NUM==0) //每TICK_NUM=100个ticks,执行print_ticks()函数输出显示
print_ticks();
break;
copy
学习时间 13分钟
操作时间 0分钟
按键次数 0次
实验次数 4次
报告字数 12148字
是否完成 完成