1.实验步骤
首先编译lab,执行如下命令:
Documents/work/code/xv6/lab$ make
+ as kern/entry.S
+ cc kern/entrypgdir.c
+ cc kern/init.c
+ cc kern/console.c
+ cc kern/monitor.c
+ cc kern/printf.c
+ cc kern/kdebug.c
+ cc lib/printfmt.c
+ cc lib/readline.c
+ cc lib/string.c
+ ld obj/kern/kernel
+ as boot/boot.S
+ cc -Os boot/main.c
+ ld boot/boot
boot block is 380 bytes (max 510)
+ mk obj/kern/kernel.img
开启两个终端窗口,分别执行如下命令:
Note:
Enter make qemu-gdb (or make qemu-nox-gdb). This starts up QEMU, but QEMU stops just before the processor executes the first instruction and waits for a debugging connection from GDB. In the second terminal, from the same directory you ran make, run make gdb.
执行make gdb命令之后显示如下:
zhaoxiao@zhaoxiao:~/Documents/work/code/xv6/lab$ make gdb
gdb -n -x .gdbinit
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.3) 7.7.1
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
.
Find the GDB manual and other documentation resources online at:
.
For help, type "help".
Type "apropos word" to search for commands related to "word".
+ target remote localhost:26000
warning: A handler for the OS ABI "GNU/Linux" is not built into this configuration
of GDB. Attempting to continue with the default i8086 settings.
The target architecture is assumed to be i8086
[f000:fff0] 0xffff0: ljmp $0xf000,$0xe05b
0x0000fff0 in ?? ()
+ symbol-file obj/kern/kernel
上一篇文档xv6 bootstrap启动分析中提到BIOS会调到0xfe05b开始进行一系列的硬件初始化工作。当这些工作都完成了,计算机的硬件都处在一个基础的就绪状态,就可以进行操作系统的引导了。当BIOS发现bootable BIOS floppy or hard disk之后,加载512字节boot sector into memory at physical addresses 0x7c00 through 0x7dff,然后jmp跳转到CS:IP=0000:7c00地址,将控制权交给boot loader。在0x7c00设置硬件断点,然后查看汇编指令(对比 boot/boot.S)如下所示:
(gdb) hbr *0x7c00
Hardware assisted breakpoint 1 at 0x7c00
(gdb) c
Continuing.
[ 0:7c00] => 0x7c00: cli
Breakpoint 1, 0x00007c00 in ?? ()
(gdb) x/5i 0x7c00
=> 0x7c00: cli
0x7c01: cld
0x7c02: xor %ax,%ax
0x7c04: mov %ax,%ds
0x7c06: mov %ax,%es
(gdb)
2.Boot loader
The boot loader 包含两个文件 boot/boot.S和boot/main.c。The boot loader must perform two main functions:
关于Load address和Link address:
Boot loader加载在RAM什么位置?
查看文件boot/Makefrag,指定text段link address为0x7c00。
在没有开启分页机制前,boot loader’s the link and load addresses are the same,look at the .text section of the boot loader:
The BIOS loads the boot sector into memory starting at address 0x7c00, so this is the boot sector’s load address. This is also where the boot sector executes from, so this is also its link address. We set the link address by passing -Ttext 0x7C00 to the linker in boot/Makefrag, so the linker will produce the correct memory addresses in the generated code.
当执行make编译后,会生成两个文件obj/boot/boot.asm和obj/kern/kernel.asm。其中boot.asm是由boot/boot.s和boot/main.c混合编译而成;kernel.asm 是由kern/init.c和kern/entry.s混合编译而成。
Kernel入口地址在哪?
bootmain函数第392行跳转到kernel,汇编指令为:call *0x10018。
obj/boot/boot.asm:
327 void
328 bootmain(void)
329 {
330 ...
390 // call the entry point from the ELF header
391 // note: does not return!
392 ((void (*)(void)) (ELFHDR->e_entry))();
393 7d61: ff 15 18 00 01 00 call *0x10018
394 }
查看0x10018内容是0x0010000c,而该值正好是kernel Entry point address。
(gdb) x/x 0x10018
0x10018: 0x0010000c
Documents/work/code/xv6/lab$ readelf -h obj/kern/kernel
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x10000c
3.Kernel
boot loader load address and link address是一致的,而在Kernel中these two addresses aren’t the same: the kernel is telling the boot loader to load it into memory at a low address (1 megabyte), but it expects to execute from a high address.
Kernels often like to be linked and run at very high virtual address, such as 0xf0100000, in order to leave the lower part of the processor’s virtual address space for user programs to use.
在kern/kernel.ld链接脚本中定义了kernel link address为0xF0100000(the link address at which the kernel code expects to run),boot loader加载kernel的load address为0x100000(where the boot loader loaded the kernel into physical memory)。如下所示:
验证如下:
小结:
lab1物理内存分布图
4.Lab1 实验
4.1.Exercise 6
Examine the 8 words of memory at 0x00100000 at the point the BIOS enters the boot loader, and then again at the point the boot loader enters the kernel. Why are they different? What is there at the second breakpoint?
实验分析:
(gdb) b *0x7c00
Breakpoint 1 at 0x7c00
(gdb) c
Continuing.
[ 0:7c00] => 0x7c00: cli
Breakpoint 1, 0x00007c00 in ?? ()
#at the point when the BIOS enters the bootloader:
(gdb) x/8w 0x00100000
0x100000: 0x00000000 0x00000000 0x00000000 0x00000000
0x100010: 0x00000000 0x00000000 0x00000000 0x00000000
#set the breakpoints in the kernel
(gdb) b *0x10000c
Breakpoint 2 at 0x10000c
(gdb) c
Continuing.
The target architecture is assumed to be i386
=> 0x10000c: movw $0x1234,0x472
Breakpoint 2, 0x0010000c in ?? ()
#at the point when bootloader enters the kernel:
(gdb) x/8w 0x00100000
0x100000: 0x1badb002 0x00000000 0xe4524ffe 0x7205c766
0x100010: 0x34000004 0x0000b812 0x220f0011 0xc0200fd8
(gdb) x/10i 0x00100000
0x100000: add 0x1bad(%eax),%dh
0x100006: add %al,(%eax)
0x100008: decb 0x52(%edi)
0x10000b: in $0x66,%al
0x10000d: movl $0xb81234,0x472
0x100017: add %dl,(%ecx)
0x100019: add %cl,(%edi)
0x10001b: and %al,%bl
0x10001d: mov %cr0,%eax
0x100020: or $0x80010001,%eax
Comparing this to the boot/kernel.asm, We see that this is exactly the beginning of the code segment of the kernel.
4.2.Exercise 7
Use QEMU and GDB to trace into the JOS kernel and stop at the movl %eax, %cr0. Examine memory at 0x00100000 and at 0xf0100000. Now, single step over that instruction using the stepi GDB command. Again, examine memory at 0x00100000 and at 0xf0100000.
#查看obj/kern/kernel.asm,找到movl %eax, %cr0对应的地址,然后设置断点:
(gdb) b *0x100025
Breakpoint 1 at 0x100025
(gdb) c
Continuing.
The target architecture is assumed to be i386
=> 0x100025: mov %eax,%cr0
Breakpoint 1, 0x00100025 in ?? ()
(gdb) x/i $pc
=> 0x100025: mov %eax,%cr0
(gdb) x/10w 0x00100000
0x100000: 0x1badb002 0x00000000 0xe4524ffe 0x7205c766
0x100010: 0x34000004 0x0000b812 0x220f0011 0xc0200fd8
0x100020: 0x0100010d 0xc0220f80
(gdb) x/10w 0xf0100000
0xf0100000 <_start+4026531828>: 0x00000000 0x00000000 0x00000000 0x00000000
0xf0100010 : 0x00000000 0x00000000 0x00000000 0x00000000
0xf0100020 : 0x00000000 0x00000000
如上所示,examine memory of the 0x00100000和0xf0100000,发现 0xf0100000没有任何内容。执行si命令,再次查看:
(gdb) si
=> 0x100028: mov $0xf010002f,%eax
0x00100028 in ?? ()
(gdb) x/10w 0x00100000
0x100000: 0x1badb002 0x00000000 0xe4524ffe 0x7205c766
0x100010: 0x34000004 0x0000b812 0x220f0011 0xc0200fd8
0x100020: 0x0100010d 0xc0220f80
(gdb) x/10w 0xf0100000
0xf0100000 <_start+4026531828>: 0x1badb002 0x00000000 0xe4524ffe 0x7205c766
0xf0100010 : 0x34000004 0x0000b812 0x220f0011 0xc0200fd8
0xf0100020 : 0x0100010d 0xc0220f80
执行si命令之后,0x00100000和0xf0100000内容一样,是因为开启了分页机制, 0x00100000的内容被映射到0xf0100000 。如果关闭分页机制又会如何?
obj/kern/kernel.asm:
# Turn on paging.
movl %cr0, %eax
f010001d: 0f 20 c0 mov %cr0,%eax
orl $(CR0_PE|CR0_PG|CR0_WP), %eax
f0100020: 0d 01 00 01 80 or $0x80010001,%eax
movl %eax, %cr0
# Now paging is enabled, but we're still running at a low EIP
# (why is this okay?). Jump up above KERNBASE before entering
# C code.
mov $relocated, %eax
f0100028: b8 2f 00 10 f0 mov $0xf010002f,%eax
jmp *%eax
f010002d: ff e0 jmp *%eax
如下所示,注释掉movl %eax, %cr0,则关闭分页机制,然后重新make,重复执行上面的指令。结果如下所示:
# Turn on paging.
movl %cr0, %eax
orl $(CR0_PE|CR0_PG|CR0_WP), %eax
#albert
#movl %eax, %cr0
(gdb) b *0x100025
Breakpoint 1 at 0x100025
(gdb) c
Continuing.
The target architecture is assumed to be i386
=> 0x100025: mov $0xf010002c,%eax
Breakpoint 1, 0x00100025 in ?? ()
(gdb) x/10w 0x00100000
0x100000: 0x1badb002 0x00000000 0xe4524ffe 0x7205c766
0x100010: 0x34000004 0x0000b812 0x220f0011 0xc0200fd8
0x100020: 0x0100010d 0x002cb880
(gdb) x/10w 0xf0100000
0xf0100000 <_start+4026531828>: 0x00000000 0x00000000 0x00000000 0x00000000
0xf0100010 : 0x00000000 0x00000000 0x00000000 0x00000000
0xf0100020 : 0x00000000 0x00000000
(gdb) si
=> 0x10002a: jmp *%eax
0x0010002a in ?? ()
(gdb) si
=> 0xf010002c : add %al,(%eax)
relocated () at kern/entry.S:76
76 movl $0x0,%ebp # nuke frame pointer
(gdb) si
Remote connection closed
另一个窗口显示:
在0x10002a处的jmp指令,要跳到 0xf010002c 处, 然而因为没有分页管理,不会进行虚拟地址映射到物理地址的转化,出现异常情况,虚拟机崩溃。
qemu-system-i386 -drive file=obj/kern/kernel.img,index=0,media=disk,format=raw -serial mon:stdio -gdb tcp::26000 -D qemu.log -S
qemu: fatal: Trying to execute code outside RAM or ROM at 0xf010002c
EAX=f010002c EBX=00010094 ECX=00000000 EDX=0000009d
ESI=00010094 EDI=00000000 EBP=00007bf8 ESP=00007bec
EIP=f010002c EFL=00000086 [--S--P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT= 00007c4c 00000017
IDT= 00000000 000003ff
CR0=00000011 CR2=00000000 CR3=00110000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
DR6=ffff0ff0 DR7=00000400
CCS=00000084 CCD=80010011 CCO=EFLAGS
EFER=0000000000000000
FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80
FPR0=0000000000000000 0000 FPR1=0000000000000000 0000
FPR2=0000000000000000 0000 FPR3=0000000000000000 0000
FPR4=0000000000000000 0000 FPR5=0000000000000000 0000
FPR6=0000000000000000 0000 FPR7=0000000000000000 0000
XMM00=00000000000000000000000000000000 XMM01=00000000000000000000000000000000
XMM02=00000000000000000000000000000000 XMM03=00000000000000000000000000000000
XMM04=00000000000000000000000000000000 XMM05=00000000000000000000000000000000
XMM06=00000000000000000000000000000000 XMM07=00000000000000000000000000000000
make: *** [qemu-gdb] Aborted (core dumped)
4.3.Exercise 9
Determine where the kernel initializes its stack, and exactly where in memory its stack is located. How does the kernel reserve space for its stack? And at which “end” of this reserved area is the stack pointer initialized to point to?
分析:
The kernel initializes its stack at in entry.S, bootstacktop address is defined in the .data section at the offset equal to KSTKSIZE . Because the stack grows down, bootstacktop is where the stack pointer will initially point to and it will grow towards lower addresses of the .data section.
kern/entry.S:
# Set the stack pointer
movl $(bootstacktop),%esp
...
.data
###################################################################
# boot stack
###################################################################
.p2align PGSHIFT # force page alignment
.globl bootstack
bootstack:
.space KSTKSIZE
.globl bootstacktop
bootstacktop:
To be more concrete where in memory the stack is located, let’s first find the .data section:
Documents/work/code/xv6/lab$ readelf -SW obj/kern/kernel
There are 11 section headers, starting at offset 0x139c0:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 5] .data PROGBITS f0108000 009000 00a300 00 WA 0 0 4096
如上所示,.data长度为0x f0108000–0xF0112300,对应查看编译生成的obj/kern/kernel.asm,其中esp初始化为0xf0110000,属于.data段。
57 # Set the stack pointer
58 movl $(bootstacktop),%esp
59 f0100034: bc 00 00 11 f0 mov $0xf0110000,%esp
栈的大小定义为8页,即8*4096 = 32768 = 0x8000 ,因此栈分配地址0xf0108000–0xf0110000。
//inc/memlayout.h
#define KSTKSIZE (8*PGSIZE) // size of a kernel stack
栈对应memlayout.h 分布如下:
* | | RW/--
* KERNBASE, ----> +------------------------------+ 0xf0000000 --+
* KSTACKTOP | CPU0's Kernel Stack | RW/-- KSTKSIZE |
* | - - - - - - - - - - - - - - -| |
* | Invalid Memory (*) | --/-- KSTKGAP |
* +------------------------------+ |
* | CPU1's Kernel Stack | RW/-- KSTKSIZE |
* | - - - - - - - - - - - - - - -| PTSIZE
* | Invalid Memory (*) | --/-- KSTKGAP |
* +------------------------------+ |
* : . : |
* : . : |
* MMIOLIM ------> +------------------------------+ 0xefc00000 --+
参考:
https://pdos.csail.mit.edu/6.828/2017/labs/lab1/
https://github.com/cloudius-systems/osv/wiki/OSv-early-boot-(MBR)