xv6 lab1

  • 学习MIT6.8.28课程 lab1
    实验系统:ubuntu系统

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

  开启两个终端窗口,分别执行如下命令:

  • Documents/work/code/xv6/lab$ make qemu-gdb
  • Documents/work/code/xv6/lab$ make gdb

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 <http://gnu.org/licenses/gpl.html>
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:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
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:

  • First, the boot loader switches the processor from real mode to 32-bit protected mode, because it is only in this mode that software can access all the memory above 1MB in the processor’s physical address space.
  • Second, the boot loader reads the kernel from the hard disk by directly accessing the IDE disk device registers via the x86’s special I/O instructions.

  关于Load address和Link address:

  • The load address(LMA) of a section is the memory address at which that section should be loaded into memory.
  • The link address(VMA) of a section is the memory address from which the section expects to execute.

  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 <entry+4>:	0x00000000	0x00000000	0x00000000	0x00000000
0xf0100020 <entry+20>:	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 <entry+4>:	0x34000004	0x0000b812	0x220f0011	0xc0200fd8
0xf0100020 <entry+20>:	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 <entry+4>:	0x00000000	0x00000000	0x00000000	0x00000000
0xf0100020 <entry+20>:	0x00000000	0x00000000
(gdb) si
=> 0x10002a:	jmp    *%eax
0x0010002a in ?? ()
(gdb) si
=> 0xf010002c <relocated>:	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)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值