├── .dir-locals.el ├── .gdbinit.tmpl ├── .gitignore ├── CODING ├── GNUmakefile ├── README.md ├── boot ├── Makefrag ├── boot.S ├── main.c └── sign.pl ├── conf ├── env.mk └── lab.mk ├── fs └── testshell.key ├── grade-lab1 ├── gradelib.py ├── handin-prep ├── inc ├── COPYRIGHT ├── assert.h ├── elf.h ├── error.h ├── kbdreg.h ├── memlayout.h ├── mmu.h ├── stab.h ├── stdarg.h ├── stdio.h ├── string.h ├── types.h └── x86.h ├── kern ├── COPYRIGHT ├── Makefrag ├── console.c ├── console.h ├── entry.S ├── entrypgdir.c ├── init.c ├── kdebug.c ├── kdebug.h ├── kernel.ld ├── monitor.c ├── monitor.h └── printf.c ├── lib ├── printfmt.c ├── readline.c └── string.c ├── mergedep.pl └── user └── sendpage.c /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((nil 2 | (indent-tabs-mode . t) 3 | (tab-width . 8)) 4 | (c-mode 5 | (c-file-style . "bsd") 6 | (c-basic-offset . 8)) 7 | (shell-mode 8 | (sh-basic-offset . 8) 9 | (sh-indentation . 8)) 10 | (python-mode 11 | (indent-tabs-mode . nil)) 12 | ) 13 | -------------------------------------------------------------------------------- /.gdbinit.tmpl: -------------------------------------------------------------------------------- 1 | set $lastcs = -1 2 | 3 | define hook-stop 4 | # There doesn't seem to be a good way to detect if we're in 16- or 5 | # 32-bit mode, but we always run with CS == 8 in 32-bit mode. 6 | if $cs == 8 || $cs == 27 7 | if $lastcs != 8 && $lastcs != 27 8 | set architecture i386 9 | end 10 | x/i $pc 11 | else 12 | if $lastcs == -1 || $lastcs == 8 || $lastcs == 27 13 | set architecture i8086 14 | end 15 | # Translate the segment:offset into a physical address 16 | printf "[%4x:%4x] ", $cs, $eip 17 | x/i $cs*16+$eip 18 | end 19 | set $lastcs = $cs 20 | end 21 | 22 | echo + target remote localhost:1234\n 23 | target remote localhost:1234 24 | 25 | # If this fails, it's probably because your GDB doesn't support ELF. 26 | # Look at the tools page at 27 | # http://pdos.csail.mit.edu/6.828/2009/tools.html 28 | # for instructions on building GDB with ELF support. 29 | echo + symbol-file obj/kern/kernel\n 30 | symbol-file obj/kern/kernel 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /obj 2 | /jos.in 3 | /jos.log 4 | /jos.out 5 | /jos.out.* 6 | /jos.cmd 7 | /.gdbinit 8 | /wget.log 9 | /qemu.pcap 10 | /qemu.pcap.* 11 | /qemu.out 12 | /qemu.log 13 | /gradelib.pyc 14 | /lab?-handin.tar.gz 15 | /lab?/ 16 | /sol?/ 17 | /myapi.key 18 | -------------------------------------------------------------------------------- /CODING: -------------------------------------------------------------------------------- 1 | JOS CODING STANDARDS 2 | 3 | It's easier on everyone if all authors working on a shared 4 | code base are consistent in the way they write their programs. 5 | We have the following conventions in our code: 6 | 7 | * No space after the name of a function in a call 8 | For example, printf("hello") not printf ("hello"). 9 | 10 | * One space after keywords "if", "for", "while", "switch". 11 | For example, if (x) not if(x). 12 | 13 | * Space before braces. 14 | For example, if (x) { not if (x){. 15 | 16 | * Function names are all lower-case separated by underscores. 17 | 18 | * Beginning-of-line indentation via tabs, not spaces. 19 | 20 | * Preprocessor macros are always UPPERCASE. 21 | There are a few grandfathered exceptions: assert, panic, 22 | static_assert, offsetof. 23 | 24 | * Pointer types have spaces: (uint16_t *) not (uint16_t*). 25 | 26 | * Multi-word names are lower_case_with_underscores. 27 | 28 | * Comments in imported code are usually C /* ... */ comments. 29 | Comments in new code are C++ style //. 30 | 31 | * In a function definition, the function name starts a new line. 32 | Then you can grep -n '^foo' */*.c to find the definition of foo. 33 | 34 | * Functions that take no arguments are declared f(void) not f(). 35 | 36 | The included .dir-locals.el file will automatically set up the basic 37 | indentation style in Emacs. 38 | -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | # 2 | # This makefile system follows the structuring conventions 3 | # recommended by Peter Miller in his excellent paper: 4 | # 5 | # Recursive Make Considered Harmful 6 | # http://aegis.sourceforge.net/auug97.pdf 7 | # 8 | OBJDIR := obj 9 | 10 | # Run 'make V=1' to turn on verbose commands, or 'make V=0' to turn them off. 11 | ifeq ($(V),1) 12 | override V = 13 | endif 14 | ifeq ($(V),0) 15 | override V = @ 16 | endif 17 | 18 | -include conf/lab.mk 19 | 20 | -include conf/env.mk 21 | 22 | LABSETUP ?= ./ 23 | 24 | TOP = . 25 | 26 | # Cross-compiler jos toolchain 27 | # 28 | # This Makefile will automatically use the cross-compiler toolchain 29 | # installed as 'i386-jos-elf-*', if one exists. If the host tools ('gcc', 30 | # 'objdump', and so forth) compile for a 32-bit x86 ELF target, that will 31 | # be detected as well. If you have the right compiler toolchain installed 32 | # using a different name, set GCCPREFIX explicitly in conf/env.mk 33 | 34 | # try to infer the correct GCCPREFIX 35 | ifndef GCCPREFIX 36 | GCCPREFIX := $(shell if i386-jos-elf-objdump -i 2>&1 | grep '^elf32-i386$$' >/dev/null 2>&1; \ 37 | then echo 'i386-jos-elf-'; \ 38 | elif objdump -i 2>&1 | grep 'elf32-i386' >/dev/null 2>&1; \ 39 | then echo ''; \ 40 | else echo "***" 1>&2; \ 41 | echo "*** Error: Couldn't find an i386-*-elf version of GCC/binutils." 1>&2; \ 42 | echo "*** Is the directory with i386-jos-elf-gcc in your PATH?" 1>&2; \ 43 | echo "*** If your i386-*-elf toolchain is installed with a command" 1>&2; \ 44 | echo "*** prefix other than 'i386-jos-elf-', set your GCCPREFIX" 1>&2; \ 45 | echo "*** environment variable to that prefix and run 'make' again." 1>&2; \ 46 | echo "*** To turn off this error, run 'gmake GCCPREFIX= ...'." 1>&2; \ 47 | echo "***" 1>&2; exit 1; fi) 48 | endif 49 | 50 | # try to infer the correct QEMU 51 | ifndef QEMU 52 | QEMU := $(shell if which qemu > /dev/null; \ 53 | then echo qemu; exit; \ 54 | elif which qemu-system-i386 > /dev/null; \ 55 | then echo qemu-system-i386; exit; \ 56 | else \ 57 | qemu=/Applications/Q.app/Contents/MacOS/i386-softmmu.app/Contents/MacOS/i386-softmmu; \ 58 | if test -x $$qemu; then echo $$qemu; exit; fi; fi; \ 59 | echo "***" 1>&2; \ 60 | echo "*** Error: Couldn't find a working QEMU executable." 1>&2; \ 61 | echo "*** Is the directory containing the qemu binary in your PATH" 1>&2; \ 62 | echo "*** or have you tried setting the QEMU variable in conf/env.mk?" 1>&2; \ 63 | echo "***" 1>&2; exit 1) 64 | endif 65 | 66 | # try to generate a unique GDB port 67 | GDBPORT := $(shell expr `id -u` % 5000 + 25000) 68 | 69 | CC := $(GCCPREFIX)gcc -pipe 70 | AS := $(GCCPREFIX)as 71 | AR := $(GCCPREFIX)ar 72 | LD := $(GCCPREFIX)ld 73 | OBJCOPY := $(GCCPREFIX)objcopy 74 | OBJDUMP := $(GCCPREFIX)objdump 75 | NM := $(GCCPREFIX)nm 76 | 77 | # Native commands 78 | NCC := gcc $(CC_VER) -pipe 79 | NATIVE_CFLAGS := $(CFLAGS) $(DEFS) $(LABDEFS) -I$(TOP) -MD -Wall 80 | TAR := gtar 81 | PERL := perl 82 | 83 | # Compiler flags 84 | # -fno-builtin is required to avoid refs to undefined functions in the kernel. 85 | # Only optimize to -O1 to discourage inlining, which complicates backtraces. 86 | CFLAGS := $(CFLAGS) $(DEFS) $(LABDEFS) -O1 -fno-builtin -I$(TOP) -MD 87 | CFLAGS += -fno-omit-frame-pointer 88 | CFLAGS += -Wall -Wno-format -Wno-unused -Werror -gstabs -m32 89 | # -fno-tree-ch prevented gcc from sometimes reordering read_ebp() before 90 | # mon_backtrace()'s function prologue on gcc version: (Debian 4.7.2-5) 4.7.2 91 | CFLAGS += -fno-tree-ch 92 | 93 | # Add -fno-stack-protector if the option exists. 94 | CFLAGS += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector) 95 | 96 | # Common linker flags 97 | LDFLAGS := -m elf_i386 98 | 99 | # Linker flags for JOS user programs 100 | ULDFLAGS := -T user/user.ld 101 | 102 | GCC_LIB := $(shell $(CC) $(CFLAGS) -print-libgcc-file-name) 103 | 104 | # Lists that the */Makefrag makefile fragments will add to 105 | OBJDIRS := 106 | 107 | # Make sure that 'all' is the first target 108 | all: 109 | 110 | # Eliminate default suffix rules 111 | .SUFFIXES: 112 | 113 | # Delete target files if there is an error (or make is interrupted) 114 | .DELETE_ON_ERROR: 115 | 116 | # make it so that no intermediate .o files are ever deleted 117 | .PRECIOUS: %.o $(OBJDIR)/boot/%.o $(OBJDIR)/kern/%.o \ 118 | $(OBJDIR)/lib/%.o $(OBJDIR)/fs/%.o $(OBJDIR)/net/%.o \ 119 | $(OBJDIR)/user/%.o 120 | 121 | KERN_CFLAGS := $(CFLAGS) -DJOS_KERNEL -gstabs 122 | USER_CFLAGS := $(CFLAGS) -DJOS_USER -gstabs 123 | 124 | # Update .vars.X if variable X has changed since the last make run. 125 | # 126 | # Rules that use variable X should depend on $(OBJDIR)/.vars.X. If 127 | # the variable's value has changed, this will update the vars file and 128 | # force a rebuild of the rule that depends on it. 129 | $(OBJDIR)/.vars.%: FORCE 130 | $(V)echo "$($*)" | cmp -s $@ || echo "$($*)" > $@ 131 | .PRECIOUS: $(OBJDIR)/.vars.% 132 | .PHONY: FORCE 133 | 134 | 135 | # Include Makefrags for subdirectories 136 | include boot/Makefrag 137 | include kern/Makefrag 138 | 139 | 140 | QEMUOPTS = -hda $(OBJDIR)/kern/kernel.img -serial mon:stdio -gdb tcp::$(GDBPORT) 141 | QEMUOPTS += $(shell if $(QEMU) -nographic -help | grep -q '^-D '; then echo '-D qemu.log'; fi) 142 | IMAGES = $(OBJDIR)/kern/kernel.img 143 | QEMUOPTS += $(QEMUEXTRA) 144 | 145 | .gdbinit: .gdbinit.tmpl 146 | sed "s/localhost:1234/localhost:$(GDBPORT)/" < $^ > $@ 147 | 148 | gdb: 149 | gdb -x .gdbinit 150 | 151 | pre-qemu: .gdbinit 152 | 153 | qemu: $(IMAGES) pre-qemu 154 | $(QEMU) $(QEMUOPTS) 155 | 156 | qemu-nox: $(IMAGES) pre-qemu 157 | @echo "***" 158 | @echo "*** Use Ctrl-a x to exit qemu" 159 | @echo "***" 160 | $(QEMU) -nographic $(QEMUOPTS) 161 | 162 | qemu-gdb: $(IMAGES) pre-qemu 163 | @echo "***" 164 | @echo "*** Now run 'make gdb'." 1>&2 165 | @echo "***" 166 | $(QEMU) $(QEMUOPTS) -S 167 | 168 | qemu-nox-gdb: $(IMAGES) pre-qemu 169 | @echo "***" 170 | @echo "*** Now run 'make gdb'." 1>&2 171 | @echo "***" 172 | $(QEMU) -nographic $(QEMUOPTS) -S 173 | 174 | print-qemu: 175 | @echo $(QEMU) 176 | 177 | print-gdbport: 178 | @echo $(GDBPORT) 179 | 180 | # For deleting the build 181 | clean: 182 | rm -rf $(OBJDIR) .gdbinit jos.in qemu.log 183 | 184 | realclean: clean 185 | rm -rf lab$(LAB).tar.gz \ 186 | jos.out $(wildcard jos.out.*) \ 187 | qemu.pcap $(wildcard qemu.pcap.*) \ 188 | myapi.key 189 | 190 | distclean: realclean 191 | rm -rf conf/gcc.mk 192 | 193 | ifneq ($(V),@) 194 | GRADEFLAGS += -v 195 | endif 196 | 197 | grade: 198 | @echo $(MAKE) clean 199 | @$(MAKE) clean || \ 200 | (echo "'make clean' failed. HINT: Do you have another running instance of JOS?" && exit 1) 201 | ./grade-lab$(LAB) $(GRADEFLAGS) 202 | 203 | git-handin: handin-check 204 | @if test -n "`git config remote.handin.url`"; then \ 205 | echo "Hand in to remote repository using 'git push handin HEAD' ..."; \ 206 | if ! git push -f handin HEAD; then \ 207 | echo ; \ 208 | echo "Hand in failed."; \ 209 | echo "As an alternative, please run 'make tarball'"; \ 210 | echo "and visit http://pdos.csail.mit.edu/6.828/submit/"; \ 211 | echo "to upload lab$(LAB)-handin.tar.gz. Thanks!"; \ 212 | false; \ 213 | fi; \ 214 | else \ 215 | echo "Hand-in repository is not configured."; \ 216 | echo "Please run 'make handin-prep' first. Thanks!"; \ 217 | false; \ 218 | fi 219 | 220 | WEBSUB = https://ccutler.scripts.mit.edu/6.828/handin.py 221 | 222 | handin: tarball-pref myapi.key 223 | @curl -f -F file=@lab$(LAB)-handin.tar.gz -F key=\ /dev/null || { \ 225 | echo ; \ 226 | echo Submit seems to have failed.; \ 227 | echo Please go to $(WEBSUB)/ and upload the tarball manually.; } 228 | 229 | handin-check: 230 | @if ! test -d .git; then \ 231 | echo No .git directory, is this a git repository?; \ 232 | false; \ 233 | fi 234 | @if test "$$(git symbolic-ref HEAD)" != refs/heads/lab$(LAB); then \ 235 | git branch; \ 236 | read -p "You are not on the lab$(LAB) branch. Hand-in the current branch? [y/N] " r; \ 237 | test "$$r" = y; \ 238 | fi 239 | @if ! git diff-files --quiet || ! git diff-index --quiet --cached HEAD; then \ 240 | git status; \ 241 | echo; \ 242 | echo "You have uncomitted changes. Please commit or stash them."; \ 243 | false; \ 244 | fi 245 | @if test -n "`git ls-files -o --exclude-standard`"; then \ 246 | git status; \ 247 | read -p "Untracked files will not be handed in. Continue? [y/N] " r; \ 248 | test "$$r" = y; \ 249 | fi 250 | 251 | tarball: handin-check 252 | git archive --format=tar HEAD | gzip > lab$(LAB)-handin.tar.gz 253 | 254 | tarball-pref: handin-check 255 | git archive --prefix=lab$(LAB)/ --format=tar HEAD | gzip > lab$(LAB)-handin.tar.gz 256 | 257 | myapi.key: 258 | @echo Get an API key for yourself by visiting $(WEBSUB) 259 | @read -p "Please enter your API key: " k; \ 260 | if test `echo -n "$$k" |wc -c` = 32 ; then \ 261 | TF=`mktemp -t tmp.XXXXXX`; \ 262 | if test "x$$TF" != "x" ; then \ 263 | echo -n "$$k" > $$TF; \ 264 | mv -f $$TF $@; \ 265 | else \ 266 | echo mktemp failed; \ 267 | false; \ 268 | fi; \ 269 | else \ 270 | echo Bad API key: $$k; \ 271 | echo An API key should be 32 characters long.; \ 272 | false; \ 273 | fi; 274 | 275 | handin-prep: 276 | @./handin-prep 277 | 278 | 279 | # This magic automatically generates makefile dependencies 280 | # for header files included from C source files we compile, 281 | # and keeps those dependencies up-to-date every time we recompile. 282 | # See 'mergedep.pl' for more information. 283 | $(OBJDIR)/.deps: $(foreach dir, $(OBJDIRS), $(wildcard $(OBJDIR)/$(dir)/*.d)) 284 | @mkdir -p $(@D) 285 | @$(PERL) mergedep.pl $@ $^ 286 | 287 | -include $(OBJDIR)/.deps 288 | 289 | always: 290 | @: 291 | 292 | .PHONY: all always \ 293 | handin git-handin tarball tarball-pref clean realclean distclean grade handin-prep handin-check 294 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JOS Lab 1 Report 2 | 3 | [TOC] 4 | 5 | ## 总体概述 6 | 本次Lab分为对实验环境的配置和熟悉,Bootloader,JOS kernel这三个部分,一步一步引入,对OS启动和初始化的过程进行了学习。 7 | 8 | ## 完成情况 9 | 10 | |Exercise#|01|02|03|04|05|06|07|08|ch|09|10|11|12| 11 | |:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:| 12 | |Status|√|√|√|√|√|√|√|√|√|√|√|√|√| 13 | 14 | * 其中ch代表challenge 15 | 16 | ### Part 1: PC Bootstrap 17 | 18 | #### Exercise 1 19 | > Familiarize yourself with the assembly language materials available on [the 6.828 reference page](http://pdos.csail.mit.edu/6.828/2012/reference.html). You don't have to read them now, but you'll almost certainly want to refer to some of this material when reading and writing x86 assembly. 20 | > 21 | > We do recommend reading the section "The Syntax" in [Brennan's Guide to Inline Assembly](http://www.delorie.com/djgpp/doc/brennan/brennan_att_inline_djgpp.html). It gives a good (and quite brief) description of the AT&T assembly syntax we'll be using with the GNU assembler in JOS. 22 | 23 | 之前在ICS、计算机组成、计算机体系结构课程中都学过/使用过了,帮助比较大的是AT&T和intel风格对比那篇文章。 24 | 25 | 26 | #### Exercise 2 27 | 28 | > Use GDB's si (Step Instruction) command to trace into the ROM BIOS for a few more instructions, and try to guess what it might be doing. You might want to look at [Phil Storrs I/O Ports Description](http://web.archive.org/web/20040404164813/members.iweb.net.au/~pstorr/pcbook/book2/book2.htm), as well as other materials on [the 6.828 reference materials page](http://pdosnew.csail.mit.edu/6.828/2014/reference.html). No need to figure out all the details - just the general idea of what the BIOS is doing first. 29 | 30 | 31 | ```c 32 | The target architecture is assumed to be i8086 33 | [f000:fff0] 0xffff0: ljmp $0xf000,$0xe05b 34 | [f000:e05b] 0xfe05b: jmp 0xfc85e 35 | ``` 36 | 由于处理器的设计,BIOS代码从`0xffff0`开始执行,这里离给BIOS分配的空间的顶端只有16字节的空间,因此连续执行了两次跳转指令,从BIOS真正开始的地方执行。 37 | ```c 38 | [f000:c85e] 0xfc85e: mov %cr0,%eax 39 | [f000:c861] 0xfc861: and $0x9fffffff,%eax 40 | [f000:c867] 0xfc867: mov %eax,%cr0 41 | ``` 42 | 这3句设置了控制寄存器cr0,将CD和NW标志清零(参考[维基百科](http://en.wikipedia.org/wiki/Control_register))。gdb查看cr0 = 0x00000010(此时处于实模式)。 43 | 44 | ```c 45 | [f000:c86a] 0xfc86a: cli 46 | [f000:c86b] 0xfc86b: cld 47 | [f000:c86c] 0xfc86c: mov $0x8f,%eax 48 | [f000:c872] 0xfc872: out %al,$0x70 49 | [f000:c874] 0xfc874: in $0x71,%al 50 | [f000:c876] 0xfc876: cmp $0x0,%al 51 | [f000:c878] 0xfc878: jne 0xfc88d 52 | ``` 53 | 这几句是在设置NMI,读写CMOS,根据网上查到的资料 54 | >Whenever you send a byte to IO port 0x70, the high order bit tells the hardware whether to disable NMIs from reaching the CPU. If the bit is on, NMI is disabled (until the next time you send a byte to Port 0x70). The low order 7 bits of any byte sent to Port 0x70 are used to address CMOS registers. [*[CMOS - OSDev Wiki](http://wiki.osdev.org/CMOS)*] 55 | > 56 | 57 | ```c 58 | void NMI_enable(void) 59 | { 60 | outb(0x70, inb(0x70)&0x7F); 61 | } 62 | 63 | void NMI_disable(void) 64 | { 65 | outb(0x70, inb(0x70)|0x80); 66 | } 67 | ``` 68 | 69 | 70 | 可此可知,0x8f中最高位为1,即关闭NMI,这里的作用应该是为了让后面读0x71端口的时候的值避免NMI可能产生的影响。0x8f低7位为0x0f,即选择了CMOS的0x0f号寄存器。这个寄存器中的值表示计算机的关闭状态(shutdown status),0x0代表正常启动。(参考[Bochs Developers Guide](http://bochs.sourceforge.net/doc/docbook/development/cmos-map.html)) 71 | 72 | 简单来说,这几句代码的意思就是在`CMOS`中检查计算机开启/关闭的状态,若正常则继续执行;否则跳转至`0xfc88d`处对其他情况进行处理。 73 | 74 | ```c 75 | [f000:c87a] 0xfc87a: xor %ax,%ax 76 | [f000:c87c] 0xfc87c: mov %ax,%ss 77 | [f000:c87e] 0xfc87e: mov $0x7000,%esp 78 | [f000:c884] 0xfc884: mov $0xf4b2c,%edx 79 | ``` 80 | 这几句设置了栈的段寄存器`%ss`和栈定寄存器`%esp`,栈的空间为`0x00000`到`0x07000`。 81 | 82 | ```c 83 | [f000:c88a] 0xfc88a: jmp 0xfc719 84 | [f000:c719] 0xfc719: mov %eax,%ecx 85 | [f000:c71c] 0xfc71c: cli 86 | [f000:c71d] 0xfc71d: cld 87 | [f000:c71e] 0xfc71e: mov $0x8f,%eax 88 | [f000:c724] 0xfc724: out %al,$0x70 89 | [f000:c726] 0xfc726: in $0x71,%al 90 | [f000:c728] 0xfc728: in $0x92,%al 91 | [f000:c72a] 0xfc72a: or $0x2,%al 92 | [f000:c72c] 0xfc72c: out %al,$0x92 93 | ``` 94 | 后面这几行代码是用来通过`System Control Port A`(`0x92`)激活`A20`总线(激活前所有地址中的20位将被清零,具体参见[这篇文章](http://www.win.tue.nl/~aeb/linux/kbd/A20.html)),准备进入保护模式。 95 | 96 | ```c 97 | [f000:c72e] 0xfc72e: lidtw %cs:-0x31cc 98 | [f000:c734] 0xfc734: lgdtw %cs:-0x3188 99 | [f000:c73a] 0xfc73a: mov %cr0,%eax 100 | [f000:c73d] 0xfc73d: or $0x1,%eax 101 | [f000:c741] 0xfc741: mov %eax,%cr0 102 | [f000:c744] 0xfc744: ljmpl $0x8,$0xfc74c 103 | ``` 104 | 加载全局/终端描述符表寄存器,设置`cr0`最低位为1,进入保护模式,并跳转到对应的代码。 105 | 106 | ```c 107 | The target architecture is assumed to be i386 108 | => 0xfc74c: mov $0x10,%eax 109 | => 0xfc751: mov %eax,%ds 110 | => 0xfc753: mov %eax,%es 111 | => 0xfc755: mov %eax,%ss 112 | => 0xfc757: mov %eax,%fs 113 | => 0xfc759: mov %eax,%gs 114 | => 0xfc75b: mov %ecx,%eax 115 | => 0xfc75d: jmp *%edx 116 | ``` 117 | 设置保护模式下的段寄存器,然后`%edx`中保存的位置。此后有很长很长的循环执行的代码,应该是`BIOS`在对各种设备进行测试和初始化。 118 | 119 | 在这个过程中CPU不断地在保护模式和实模式之间转换,指令的地址也在`0xcxxxx`~`0xfxxxx`和`0xfxxxxxxx`之间转换。 120 | 121 | 指令在高位时CPU处于保护模式,将高位的设备映射到低位: 122 | ```c 123 | => 0xffff3d5d: rep movsb %ds:(%esi),%es:(%edi) 124 | 0xffff3d5d in ?? () 125 | (gdb) p/x $ds 126 | $1 = 0x10 127 | (gdb) p/x $esi 128 | $2 = 0xfffe667a 129 | (gdb) p/x $es 130 | $3 = 0x10 131 | (gdb) p/x $edi 132 | $4 = 0xe667a 133 | ``` 134 | 在低位时,CPU处于实模式,执行对设备的检查、测试、初始化等操作。 135 | 136 | 自检完成之后`BIOS`会寻找一个可启动的设备(硬盘、光驱、软盘等),并从中(硬盘中的前512字节)载入并将控制权转交给`bootloader`。 137 | 138 | ### Part 2: The Boot Loader 139 | 140 | #### Exercise 3 141 | 142 | > Take a look at the lab tools guide, especially the section on GDB commands. Even if you're familiar with GDB, this includes some esoteric GDB commands that are useful for OS work. 143 | > 144 | >Set a breakpoint at address 0x7c00, which is where the boot sector will be loaded. Continue execution until that breakpoint. Trace through the code in boot/boot.S, using the source code and the disassembly file obj/boot/boot.asm to keep track of where you are. Also use the x/i command in GDB to disassemble sequences of instructions in the boot loader, and compare the original boot loader source code with both the disassembly in obj/boot/boot.asm and GDB. 145 | 146 | >Trace into bootmain() in boot/main.c, and then into readsect(). Identify the exact assembly instructions that correspond to each of the statements in readsect(). Trace through the rest of readsect() and back out into bootmain(), and identify the begin and end of the for loop that reads the remaining sectors of the kernel from the disk. Find out what code will run when the loop is finished, set a breakpoint there, and continue to that breakpoint. Then step through the remainder of the boot loader. 147 | 148 | - At what point does the processor start executing 32-bit code? 149 | `ljmp $PROT_MODE_CSEG, $protcseg`跳转之后从`movw $PROT_MODE_DSEG, %ax`开始执行32-bit代码。 150 | 151 | - What exactly causes the switch from 16- to 32-bit mode? 152 | ``` 153 | lgdt gdtdesc 154 | movl %cr0, %eax 155 | orl $CR0_PE_ON, %eax 156 | movl %eax, %cr0 157 | ``` 158 | 其中加载了全局描述符表,然后将`cr0`中的PE位置`1`,即实现从实模式到保护模式的转换。 159 | 160 | - What is the last instruction of the boot loader executed? 161 | `main.c`中的`((void (*)(void)) (ELFHDR->e_entry))();`在GDB中反汇编代码中对应:`0x7d5e: call *0x10018`,即跳转到`kernel`去。 162 | 163 | - What is the first instruction of the kernel it just loaded? 164 | 在GDB中查看:`0x10000c: movw $0x1234,0x472`; 165 | 在`entry.S`中对应: 166 | ``` 167 | .globl entry 168 | entry: 169 | movw $0x1234,0x472 # warm boot 170 | ``` 171 | 172 | - Where is the first instruction of the kernel? 173 | 由上一问可得,或者直接在终端中输入: 174 | ``` 175 | $ objdump -f obj/kern/kernel 176 | 177 | obj/kern/kernel: file format elf32-i386 178 | architecture: i386, flags 0x00000112: 179 | EXEC_P, HAS_SYMS, D_PAGED 180 | start address 0x0010000c 181 | 182 | $ 183 | ``` 184 | `kernel`第一条指令的地址为`0x0010000c`,在`entry.S`中的对应第`44`行。 185 | 186 | - How does the boot loader decide how many sectors it must read in order to fetch the entire kernel from disk? Where does it find this information? 187 | `bootloader`会先从硬盘中读入`ELF File Header`: 188 | 189 | ```c 190 | readseg((uint32_t) ELFHDR, SECTSIZE*8, 0); 191 | ``` 192 | 193 | 由(`ELFHDR`的地址 + 程序头表的文件便宜`e_phoff`)能得到开始其中保存的起始程序头的地址`ph`,`eph = ph + ELF Header中总的程序头个数e_phnum`为结束地址。 194 | 195 | 利用`ph`和`eph`可遍历每一个程序头,并依次从中读取出`kernel`的内容: 196 | 197 | ```c 198 | readseg(ph->p_pa, ph->p_memsz, ph->p_offset) 199 | ``` 200 | 201 | #### Exercise 4 202 | 203 | >Read about programming with pointers in C. The best reference for the C language is The C Programming Language by Brian Kernighan and Dennis Ritchie (known as 'K&R'). We recommend that students purchase this book (here is an [Amazon Link](http://www.amazon.com/C-Programming-Language-2nd/dp/0131103628/sr=8-1/qid=1157812738/ref=pd_bbs_1/104-1502762-1803102?ie=UTF8&s=books)) or find one of [MIT's 7 copies](http://library.mit.edu/F/AI9Y4SJ2L5ELEE2TAQUAAR44XV5RTTQHE47P9MKP5GQDLR9A8X-10422?func=item-global&doc_library=MIT01&doc_number=000355242&year=&volume=&sub_library=). 204 | 205 | >Read 5.1 (Pointers and Addresses) through 5.5 (Character Pointers and Functions) in K&R. Then download the code for [pointers.c](http://pdosnew.csail.mit.edu/6.828/2014/labs/lab1/pointers.c), run it, and make sure you understand where all of the printed values come from. In particular, make sure you understand where the pointer addresses in lines 1 and 6 come from, how all the values in lines 2 through 4 get there, and why the values printed in line 5 are seemingly corrupted. 206 | 207 | > There are other references on pointers in C (e.g., A tutorial by [Ted Jensen](http://pdosnew.csail.mit.edu/6.828/2014/readings/pointers.pdf) that cites K&R heavily), though not as strongly recommended. 208 | > 209 | > Warning: Unless you are already thoroughly versed in C, do not skip or even skim this reading exercise. If you do not really understand pointers in C, you will suffer untold pain and misery in subsequent labs, and then eventually come to understand them the hard way. Trust us; you don't want to find out what "the hard way" is. 210 | 211 | 浏览了一遍K&R的指针部分,读了`pointers.c`,里面涉及的问题原来都见过的,这部分比较简单。主要值得注意的就是C中指针做加法时增加的大小为指针类型对应的单位长度。 212 | 213 | #### Exercise 5 214 | 215 | >Trace through the first few instructions of the boot loader again and identify the first instruction that would "break" or otherwise do the wrong thing if you were to get the boot loader's link address wrong. Then change the link address in `boot/Makefrag` to something wrong, run `make clean`, recompile the lab with `make`, and trace into the boot loader again to see what happens. Don't forget to change the link address back and `make clean` again afterward! 216 | 217 | 将link address改成了`0x7d00`,执行时发生了错误, 218 | ``` 219 | The target architecture is assumed to be i8086 220 | [f000:fff0] 0xffff0: ljmp $0xf000,$0xe05b 221 | 0x0000fff0 in ?? () 222 | + symbol-file obj/kern/kernel 223 | (gdb) c 224 | Continuing. 225 | 226 | Program received signal SIGTRAP, Trace/breakpoint trap. 227 | [ 0:7c2d] => 0x7c2d: ljmp $0x8,$0x7d32 228 | 0x00007c2d in ?? () 229 | (gdb) 230 | ``` 231 | 从`0x7c00`开始单步执行,中间出现了这三处跳转: 232 | ``` 233 | ... 234 | [ 0:7c0e] => 0x7c0e: jne 0x7c0a 235 | 0x00007c0e in ?? () 236 | (gdb) 237 | ... 238 | [ 0:7c18] => 0x7c18: jne 0x7c14 239 | 0x00007c18 in ?? () 240 | (gdb) 241 | ... 242 | [ 0:7c2d] => 0x7c2d: ljmp $0x8,$0x7d32 243 | 0x00007c2d in ?? () 244 | (gdb) 245 | ... 246 | ``` 247 | 248 | 其中前两处与link address设置为`0x7c00`时一致,因为这里使用的条件跳转指令为相对跳转,与link address的值无关,不会受到影响;而相反的,执行到第三条跳转`ljmp`时发生了错误,这是因为`ljmp`是利用其后两个参数跳转到某一绝对的地址上,此时如果link address与load address不一致了,那么跳转的目标地址也是错误的。 249 | 250 | ### Part 3: The Kernel 251 | 252 | #### Exercise 6 253 | 254 | >We can examine memory using GDB's `x` command. The [GDB manual](https://sourceware.org/gdb/current/onlinedocs/gdb/Memory.html#Memory) has full details, but for now, it is enough to know that the command `x/Nx ADDR` prints N words of memory at ADDR. (Note that both 'x's in the command are lowercase.) Warning: The size of a word is not a universal standard. In GNU assembly, a word is two bytes (the 'w' in xorw, which stands for word, means 2 bytes). 255 | 256 | >Reset the machine (exit QEMU/GDB and start them again). 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? (You do not really need to use QEMU to answer this question. Just think.) 257 | 258 | 结果会不同,从`kernel.asm`可知`0xf0100000`是`kernel`的代码的起始处。在之前的练习中`0x0010000c`为`kernel`第一条语句的link address,其对应的load address为`0xf010000c`。 259 | ``` 260 | f0100000: 02 b0 ad 1b 00 00 add 0x1bad(%eax),%dh 261 | f0100006: 00 00 add %al,(%eax) 262 | f0100008: fe 4f 52 decb 0x52(%edi) 263 | f010000b: e4 66 in $0x66,%al 264 | 265 | f010000c : 266 | f010000c: 66 c7 05 72 04 00 00 movw $0x1234,0x472 267 | f0100013: 34 12 268 | f0100015: b8 00 00 11 00 mov $0x110000,%eax 269 | f010001a: 0f 22 d8 mov %eax,%cr3 270 | f010001d: 0f 20 c0 mov %cr0,%eax 271 | ``` 272 | 273 | 由此可知`0x00100000`为`kernel`的起始位置的link address,其后开始8个字应该为`kernel`代码开始的8个字(这里按照gdb的默认值,认为4bytes为1word,参考[GDB Manual](https://sourceware.org/gdb/current/onlinedocs/gdb/Memory.html#Memory)),即 274 | ``` 275 | 02 b0 ad 1b 00 00 00 00 fe 4f 52 e4 66 c7 05 72 276 | 04 00 00 34 12 b8 00 00 11 00 0f 22 d8 0f 20 c0 277 | ``` 278 | 其中注意`f010000b`后的第二个字节`66`与`f010000c`后的第一个字节`66`为同一字节,只能算一次。 279 | 280 | 按小端法以字为单位组合: 281 | ``` 282 | 0x1badb002 0x00000000 0xe4524ffe 0x7205c766 283 | 0x34000004 0x0000b812 0x220f0011 0xc0200fd8 284 | ``` 285 | 286 | 下面用gdb设置断点运行验证猜想: 287 | ``` 288 | (gdb) break *0x7c00 289 | Breakpoint 1 at 0x7c00 290 | (gdb) break *0x10000c 291 | Breakpoint 2 at 0x10000c 292 | (gdb) c 293 | Continuing. 294 | [ 0:7c00] => 0x7c00: cli 295 | 296 | Breakpoint 1, 0x00007c00 in ?? () 297 | (gdb) x/8x 0x00100000 298 | 0x100000: 0x00000000 0x00000000 0x00000000 0x00000000 299 | 0x100010: 0x00000000 0x00000000 0x00000000 0x00000000 300 | (gdb) c 301 | Continuing. 302 | The target architecture is assumed to be i386 303 | => 0x10000c: movw $0x1234,0x472 304 | 305 | Breakpoint 2, 0x0010000c in ?? () 306 | (gdb) x/8x 0x00100000 307 | 0x100000: 0x1badb002 0x00000000 0xe4524ffe 0x7205c766 308 | 0x100010: 0x34000004 0x0000b812 0x220f0011 0xc0200fd8 309 | (gdb) 310 | 311 | ``` 312 | 最后的结果和之前完全一致,猜想得证。 313 | 314 | #### Exercise 7 315 | 316 | >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. Make sure you understand what just happened. 317 | 318 | >What is the first instruction after the new mapping is established that would fail to work properly if the mapping weren't in place? Comment out the `movl %eax, %cr0` in `kern/entry.S`, trace into it, and see if you were right. 319 | 320 | ```c 321 | The target architecture is assumed to be i386 322 | => 0x100025: mov %eax,%cr0 323 | 324 | Breakpoint 1, 0x00100025 in ?? () 325 | (gdb) x/8x 0x00100000 326 | 0x100000: 0x1badb002 0x00000000 0xe4524ffe 0x7205c766 327 | 0x100010: 0x34000004 0x0000b812 0x220f0011 0xc0200fd8 328 | (gdb) x/8x 0xf0100000 329 | 0xf0100000 <_start+4026531828>: 0xffffffff 0xffffffff 0xffffffff 0xffffffff 330 | 0xf0100010 : 0xffffffff 0xffffffff 0xffffffff 0xffffffff 331 | ``` 332 | 333 | ```c 334 | (gdb) si 335 | => 0x100028: mov $0xf010002f,%eax 336 | 0x00100028 in ?? () 337 | (gdb) x/8x 0x00100000 338 | 0x100000: 0x1badb002 0x00000000 0xe4524ffe 0x7205c766 339 | 0x100010: 0x34000004 0x0000b812 0x220f0011 0xc0200fd8 340 | (gdb) x/8x 0xf0100000 341 | 0xf0100000 <_start+4026531828>: 0x1badb002 0x00000000 0xe4524ffe 0x7205c766 342 | 0xf0100010 : 0x34000004 0x0000b812 0x220f0011 0xc0200fd8 343 | (gdb) 344 | ``` 345 | 对比可以看出执行`movl %eax, %cr0`语句前,`0xf0100000`后的内容为某种默认值,与`0x00100000`后的内容不同;而执行后两处的却变得相同了。 346 | 347 | 这是因为 348 | ``` 349 | # Turn on paging. 350 | movl %cr0, %eax 351 | orl $(CR0_PE|CR0_PG|CR0_WP), %eax 352 | movl %eax, %cr0 353 | ``` 354 | 这段代码打开了paging机制,虚拟内存地址被映射到物理内存空间。 355 | 356 | 在`kern/entrypgdir.c`中定义用`[KERNBASE, KERNBASE+4MB)`的虚拟地址映射物理地址`[0, 4MB)`的空间。`KERNBASE`在`inc/memlayout.h`中定义为`0xF0000000`。 357 | 358 | 因此在这句话执行后,`0xf0100000`后的地址也自然也被映射到了`0x00100000`后。 359 | 360 | 尝试注释掉`movl %eax, %cr0`语句后,`0xf0100000`后的内容前后没有改变,且当继续执行时会因为访问了无效的空间范围而出错: 361 | ```c 362 | Program received signal SIGTRAP, Trace/breakpoint trap. 363 | The target architecture is assumed to be i386 364 | => 0xf010002c : (bad) 365 | relocated () at kern/entry.S:74 366 | 74 movl $0x0,%ebp # nuke frame pointer 367 | ``` 368 | 369 | #### Exercise 8 370 | 371 | >We have omitted a small fragment of code - the code necessary to print octal numbers using patterns of the form "%o". Find and fill in this code fragment. 372 | 373 | 将`lib/printfmt.c`中的`void vprintfmt(...);`函数中一处`switch`语句的`case 'o'`下更改为: 374 | ``` 375 | case 'o': 376 | num = getuint(&ap, lflag); 377 | base = 8; 378 | goto number; 379 | ``` 380 | 381 | `make qemu`运行可得到结果: 382 | ``` 383 | 6828 decimal is 15254 octal! 384 | ``` 385 | 386 | --- 387 | 388 | >Be able to answer the following questions: 389 | 390 | - Explain the interface between `printf.c` and `console.c`. Specifically, what function does `console.c` export? How is this function used by printf.c? 391 | 392 | `console.c`实现了一些与向显示器等硬件进行交互的函数,并留有封装好的输入输出接口`getchar()`与`cputchar()`,方便外部调用。其中`printf.c`在其`putch()`函数中使用`cputchar()`来实现输出: 393 | ```c 394 | // kern/printf.c 395 | static void 396 | putch(int ch, int *cnt) 397 | { 398 | cputchar(ch); 399 | *cnt++; 400 | } 401 | ``` 402 | 403 | ``` 404 | // kern/console.c 405 | void 406 | cputchar(int c) 407 | { 408 | cons_putc(c); 409 | } 410 | ``` 411 | 412 | - Explain the following from console.c: 413 | ``` 414 | 1 if (crt_pos >= CRT_SIZE) { 415 | 2 int i; 416 | 3 memcpy(crt_buf, crt_buf + CRT_COLS, (CRT_SIZE - CRT_COLS) * sizeof(uint16_t)); 417 | 4 for (i = CRT_SIZE - CRT_COLS; i < CRT_SIZE; i++) 418 | 5 crt_buf[i] = 0x0700 | ' '; 419 | 6 crt_pos -= CRT_COLS; 420 | 7 } 421 | ``` 422 | 423 | 其中`CRT_COLS`在`kern/console.h`中定义,表示一行有多少列(即多少个字符)。因此这段代码的作用就是当屏幕已经满了的时候,将最上一行舍弃,之后每一行的内容复制到上一行中,然后最后一行清空。 424 | 425 | - For the following questions you might wish to consult the notes for Lecture 2. These notes cover GCC's calling convention on the x86. 426 | Trace the execution of the following code step-by-step: 427 | ``` 428 | int x = 1, y = 3, z = 4; 429 | cprintf("x %d, y %x, z %d\n", x, y, z); 430 | ``` 431 | - In the call to `cprintf()`, to what does `fmt` point? To what does `ap` point? 432 | `fmt`指向"x %d, y %x, z %d\n"的地址,`ap`可能有的第二个参数,这里即为`x`的地址。 433 | 434 | - List (in order of execution) each call to `cons_putc`, `va_arg`, and `vcprintf`. For `cons_putc`, list its argument as well. For `va_arg`, list what `ap` points to before and after the call. For `vcprintf` list the values of its two arguments. 435 | ``` 436 | =>cprintf 437 | =>vcprintf("x %d, y %x, z %d\n", 12(%ebp)) 438 | =>cons_putc('x') 439 | =>cons_putc(32) 440 | =>va_arg (uint32_t *)ebp+3->(uint32_t *)ebp+4 441 | =>cons_putc('1') 442 | =>cons_putc(',') 443 | =>cons_putc(32) 444 | =>cons_putc('y') 445 | =>cons_putc(' ') 446 | =>va_arg (uint32_t *)ebp+4->(uint32_t *)ebp+5 447 | =>cons_putc('3') 448 | =>cons_putc(',') 449 | =>cons_putc(' ') 450 | =>cons_putc('z') 451 | =>cons_putc(' ') 452 | =>va_arg (uint32_t *)ebp+5->(uint32_t *)ebp+6 453 | =>cons_putc('4') 454 | =>cons_putc('\n') 455 | ``` 456 | 457 | - Run the following code. 458 | ``` 459 | unsigned int i = 0x00646c72; 460 | cprintf("H%x Wo%s", 57616, &i); 461 | ``` 462 | What is the output? Explain how this output is arrived at in the step-by-step manner of the previous exercise. [Here's an ASCII table](http://web.cs.mun.ca/~michael/c/ascii-table.html) that maps bytes to characters. 463 | The output depends on that fact that the x86 is little-endian. If the x86 were instead big-endian what would you set i to in order to yield the same output? Would you need to change 57616 to a different value? 464 | 465 | [Here's a description of little- and big-endian](http://www.webopedia.com/TERM/b/big_endian.html) and [a more whimsical description](http://www.networksorcery.com/enp/ien/ien137.txt). 466 | 467 | 输出是`He110 World`, 468 | 其中`57616 = 0xe110`,`i`小尾:`0x72`、`0x6c`、`0x64`、`0x00` 469 | 执行过程: 470 | ``` 471 | =>cprintf 472 | =>vcprintf("H%x Wo%s", 12(%ebp)) 473 | =>cons_putc('H') 474 | =>va_arg (uint32_t *)ebp+3->(uint32_t *)ebp+4 475 | =>cons_putc('e') 476 | =>cons_putc('1') 477 | =>cons_putc('1') 478 | =>cons_putc('0') 479 | =>cons_putc(' ') 480 | =>cons_putc('W') 481 | =>cons_putc('o') 482 | =>va_arg (uint32_t *)ebp+4->(uint32_t *)ebp+5 483 | =>cons_putc(114) // 0x72 'r' 484 | =>cons_putc(108) // 0x6c 'l' 485 | =>cons_putc(100) // 0x64 'd' 486 | ``` 487 | 488 | 若使用大尾序,只需将`i`改为'0x726c6400',`57616`无需更改。 489 | 490 | - In the following code, what is going to be printed after '`y=`'? (note: the answer is not a specific value.) Why does this happen? 491 | ``` 492 | cprintf("x=%d y=%d", 3); 493 | ``` 494 | 读取完3后`ap`指向`(uint32_t *)ebp+4`,在处理到`%d`时会再次调用`va_arg`,这是会将`ap`现在所指的位置的内容输出出来,具体是什么不得而知。 495 | 496 | - Let's say that GCC changed its calling convention so that it pushed arguments on the stack in declaration order, so that the last argument is pushed last. How would you have to change cprintf or its interface so that it would still be possible to pass it a variable number of arguments? 497 | 498 | 要实现这一点必须要能够倒着读传入栈中的参数,这就需要对`va_arg`和`va_start`进行改写。`JOS2014`中的`va_arg`声明为: 499 | ``` 500 | #define va_arg(ap, type) __builtin_va_arg(ap, type) 501 | ``` 502 | 无法直接修改,考虑重写`va_arg`。在[网上](http://research.microsoft.com/en-us/um/redmond/projects/invisible/include/stdarg.h.htm)找到`va_arg`的一种可能的实现: 503 | ```c 504 | > #define va_arg(_ap_, _type_) \ 505 | ((_ap_ = (char *) ((__alignof__ (_type_) > 4 \ 506 | ? __ROUND((int)_ap_,8) : __ROUND((int)_ap_,4)) \ 507 | + __ROUND(sizeof(_type_),4))), \ 508 | *(_type_ *) (void *) (_ap_ - __ROUND(sizeof(_type_),4))) 509 | ``` 510 | 简化之后可以写为: 511 | ```c 512 | > #define va_arg(ap,t) \ 513 | (*(t *)((ap += __va_size(t)) - __va_size(t))) 514 | ``` 515 | 若要实现倒序访问,可将其修改为: 516 | ```c 517 | > #define va_arg(ap,t) \ 518 | (*(t *)((ap -= __va_size(t)) + __va_size(t))) 519 | ``` 520 | 此外,需要重写`va_start`,使其在初始化时指向最后一个参数: 521 | ```c 522 | > #define va_start(ap, last) \ 523 | ((ap) = (va_list)&(last) - __va_size(last)) 524 | ``` 525 | 526 | ##### Challenge 527 | 528 | >Enhance the console to allow text to be printed in different colors. The traditional way to do this is to make it interpret [ANSI escape sequences](http://rrbrandt.dee.ufcg.edu.br/en/docs/ansi/) embedded in the text strings printed to the console, but you may use any mechanism you like. There is plenty of information on [the 6.828 reference page](http://pdosnew.csail.mit.edu/6.828/2014/reference.html) and elsewhere on the web on programming the VGA display hardware. If you're feeling really adventurous, you could try switching the VGA hardware into a graphics mode and making the console draw text onto the graphical frame buffer. 529 | 530 | 利用问题中给的 [ANSI escape sequences](http://rrbrandt.dee.ufcg.edu.br/en/docs/ansi/)即可完成。 531 | 532 | 作为演示修改了`monitor.c`中的内容: 533 | 534 | ``` 535 | cprintf("\033[31mWelcome \033[32mto \033[33mthe \033[34mJOS \033[35mkernel \033[36mmonitor!\033[0m\n"); 536 | ``` 537 | 538 | ![enter image description here](http://ww2.sinaimg.cn/large/6313a6d8jw1eq1ooh800xj20g403kwex.jpg) 539 | 540 | #### Exercise 9 541 | 542 | >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? 543 | 544 | `kernel`初始化栈的语句在`entry.S`中: 545 | ``` 546 | # Set the stack pointer 547 | movl $(bootstacktop),%esp 548 | ``` 549 | `bootstacktop`也在`entry.S`中定义了: 550 | ``` 551 | ################################################################### 552 | # boot stack 553 | ################################################################### 554 | .p2align PGSHIFT # force page alignment 555 | .globl bootstack 556 | bootstack: 557 | .space KSTKSIZE 558 | .globl bootstacktop 559 | bootstacktop: 560 | ``` 561 | 空间的预留靠`.space KSTKSIZE`实现。 562 | 563 | 栈是由高地址向低地址生长,因此栈顶指针初始化时指向较高的一端,利用`gdb`可以得知其初始值为`0xf0110000`。 564 | 565 | ``` 566 | => 0xf0100034 : mov $0xf0110000,%esp 567 | relocated () at kern/entry.S:77 568 | 77 movl $(bootstacktop),%esp 569 | (gdb) si 570 | => 0xf0100039 : call 0xf0100094 571 | 80 call i386_init 572 | (gdb) p $esp 573 | $1 = (void *) 0xf0110000 574 | (gdb) 575 | ``` 576 | 577 | 578 | #### Exercise 10 579 | 580 | >To become familiar with the C calling conventions on the x86, find the address of the `test_backtrace` function in `obj/kern/kernel.asm`, set a breakpoint there, and examine what happens each time it gets called after the kernel starts. How many 32-bit words does each recursive nesting level of `test_backtrace` push on the stack, and what are those words? 581 | 582 | >Note that, for this exercise to work properly, you should be using the patched version of QEMU available on the [tools](http://pdosnew.csail.mit.edu/6.828/2014/tools.html) page or on Athena. Otherwise, you'll have to manually translate all breakpoint and memory addresses to linear addresses. 583 | 584 | 在`obj/kern/kernel.asm`中找到`test_backtrace`,可知其起始地址为`0xf0100040`: 585 | ``` 586 | 70: f0100040 : 587 | 71 #include 588 | 72 589 | 73 // Test the stack backtrace function (lab 1 only) 590 | 74 void 591 | 75: test_backtrace(int x) 592 | 76 { 593 | 77 f0100040: 55 push %ebp 594 | .. 595 | ``` 596 | 597 | 在`obj/kern/kernel.asm`中找到调用`test_backtrace`的指令地址`0xf01000cf`,在此处设置断点后查看此时的栈指针`esp = 0xf010ffe0`。 598 | 599 | 在`0xf0100040`处设置断点,反复执行几次之后用`gdb`打出栈内内容:(其中每**8个32-bit words**为1次`test_backtrace`调用后放入栈中的内容) 600 | ``` 601 | (gdb) c 602 | Continuing. 603 | => 0xf0100040 : push %ebp 604 | 605 | Breakpoint 2, test_backtrace (x=0) at kern/init.c:13 606 | 13 { 607 | (gdb) si 608 | => 0xf0100041 : mov %esp,%ebp 609 | 0xf0100041 13 { 610 | (gdb) x/48x $esp 611 | 0xf010ff58: 0xf010ff78 0xf0100068 0x00000001 0x00000002 612 | 0xf010ff68: 0xf010ff98 0x00000000 0xf010089d 0x00000003 613 | 0xf010ff78: 0xf010ff98 0xf0100068 0x00000002 0x00000003 614 | 0xf010ff88: 0xf010ffb8 0x00000000 0xf010089d 0x00000004 615 | 0xf010ff98: 0xf010ffb8 0xf0100068 0x00000003 0x00000004 616 | 0xf010ffa8: 0x00000000 0x00000000 0x00000000 0x00000005 617 | 0xf010ffb8: 0xf010ffd8 0xf0100068 0x00000004 0x00000005 618 | 0xf010ffc8: 0x00000000 0x00010094 0x00010094 0x00010094 619 | 0xf010ffd8: 0xf010fff8 0xf01000d4 0x00000005 0x00001aac 620 | 0xf010ffe8: 0x00000684 0x00000000 0x00000000 0x00000000 621 | 0xf010fff8: 0x00000000 0xf010003e 0x00111021 0x00000000 622 | 0xf0110008 : 0x00000000 0x00000000 0x00000000 0x00000000 623 | ``` 624 | 每两行为1次调用产生的内容,可以看出每两行的第一个值(`%ebp`)为刚push进栈的(调用链中上一层函数的)帧指针的值;第三个值(`%ebp+8`)为此次调用时传入的参数,其值最开始为5,每次调用减少1,直到0时返回。 625 | 626 | 继续在`kernel.asm`中确定每个地址对应位置 627 | 628 | ``` 629 | 93 f0100062: 50 push %eax 630 | 94: f0100063: e8 d8 ff ff ff call f0100040 631 | 95 f0100068: 83 c4 10 add $0x10,%esp 632 | 633 | ... 634 | 635 | 150 f01000c8: c7 04 24 05 00 00 00 movl $0x5,(%esp) 636 | 151: f01000cf: e8 6c ff ff ff call f0100040 637 | 152 f01000d4: 83 c4 10 add $0x10,%esp 638 | 639 | ``` 640 | 栈中第二列(`%ebp+4`)的`0xf0100068`和`0xf01000d4`皆为这一层`test_backtrace`的返回地址,前者为`test_backtrace`内的迭代调用(语句的下一句,下同),后者为`i386_init`函数中的调用。 641 | 642 | 其他几个位置的值则是`test_backtrace`在执行过程中压入栈中的内容(如`%ebx`)。 643 | 644 | #### Exercise 11 645 | 646 | >Implement the backtrace function as specified above. Use the same format as in the example, since otherwise the grading script will be confused. When you think you have it working right, run `make grade` to see if its output conforms to what our grading script expects, and fix it if it doesn't. After you have handed in your Lab 1 code, you are welcome to change the output format of the backtrace function any way you like. 647 | 648 | >If you use `read_ebp()`, note that GCC may generate "optimized" code that calls `read_ebp()` before `mon_backtrace()`'s function prologue, which results in an incomplete stack trace (the stack frame of the most recent function call is missing). While we have tried to disable optimizations that cause this reordering, you may want to examine the assembly of `mon_backtrace()` and make sure the call to `read_ebp()` is happening after the function prologue. 649 | 650 | ``` 651 | Stack backtrace: 652 | ebp f0109e58 eip f0100a62 args 00000001 f0109e80 f0109e98 f0100ed2 00000031 653 | ebp f0109ed8 eip f01000d6 args 00000000 00000000 f0100058 f0109f28 00000061 654 | ... 655 | ``` 656 | 其中`ebp`为当前函数调用的帧指针,`eip`为函数返回地址(即调用当前函数的语句之后的一条语句),`args`为传给当前函数的参数。 657 | 658 | `guide`中的两个问题: 659 | > The return instruction pointer typically points to the instruction after the call instruction (why?) 660 | 661 | 返回地址指向调用指令的下一行是因为在体系结构的设计中,每次CPU完成一次取值会立即令`PC`指向下一条语句,此后这个`PC`会被压入栈作为返回地址。(实际上也应该如此,反之若压入栈的为调用语句,则会无限循环调用该函数) 662 | 663 | >Why can't the backtrace code detect how many arguments there actually are? How could this limitation be fixed? 664 | 665 | `backtrace`无法确定参数的数量是因为这个函数并不明白栈中数值的意义,也不知道调用它的函数实际上传给它了几个参数。要修正这个限制,可以规定在设置好参数后,再向栈中传一个magic number来表示参数列表的结束;或者在传入参数前先传入参数数量也可以解决这个问题。 666 | 667 | 至于循环输出`backtrace`的边界,可以由**Exercise 10**中输出的栈中内容看出,`kernel`代码的最外层`ebp`为`0x00000000`。实现代码: 668 | 669 | ``` 670 | int 671 | mon_backtrace(int argc, char **argv, struct Trapframe *tf) 672 | { 673 | uintptr_t ebp, eip, args[5]; 674 | 675 | cprintf("Stack backtrace:\n"); 676 | 677 | for (ebp = read_ebp(); ebp != 0; ebp = *(uintptr_t *)ebp) { 678 | eip = *((uintptr_t *)ebp + 1); 679 | args[0] = *((uintptr_t *)ebp + 2); 680 | args[1] = *((uintptr_t *)ebp + 3); 681 | args[2] = *((uintptr_t *)ebp + 4); 682 | args[3] = *((uintptr_t *)ebp + 5); 683 | args[4] = *((uintptr_t *)ebp + 6); 684 | cprintf(" ebp %08x eip %08x args %08x %08x %08x %08x %08x\n", ebp, eip, args[0], args[1], args[2], args[3], args[4]); 685 | } 686 | return 0; 687 | } 688 | ``` 689 | 690 | 最后将其加入到"the kernel monitor's command list"中,即可在JOS运行时用`backtrace`指令查看stack backtrace: 691 | ``` 692 | static struct Command commands[] = { 693 | { "help", "Display this list of commands", mon_help }, 694 | { "kerninfo", "Display information about the kernel", mon_kerninfo }, 695 | { "backtrace", "Display stack backtrace", mon_backtrace }, 696 | }; 697 | ``` 698 | 699 | #### Exercise 12 700 | 701 | >Modify your stack backtrace function to display, for each eip, the function name, source file name, and line number corresponding to that eip. 702 | 703 | 为了输出`eip`的调试信息,首先去`kern/kdebug.h`中查看定义: 704 | 705 | ```c 706 | // Debug information about a particular instruction pointer 707 | struct Eipdebuginfo { 708 | const char *eip_file; // Source code filename for EIP 709 | int eip_line; // Source code linenumber for EIP 710 | 711 | const char *eip_fn_name; // Name of function containing EIP 712 | // - Note: not null terminated! 713 | int eip_fn_namelen; // Length of function name 714 | uintptr_t eip_fn_addr; // Address of start of function 715 | int eip_fn_narg; // Number of function arguments 716 | }; 717 | 718 | int debuginfo_eip(uintptr_t addr, struct Eipdebuginfo *info); 719 | ``` 720 | 721 | 先在`debuginfo_eip`中添加`stab_binsearch`以搜索行号: 722 | 723 | ``` 724 | stab_binsearch(stabs, &lline, &rline, N_SLINE, addr); 725 | if (lline > rline) 726 | return -1; 727 | info->eip_line = stabs[lline].n_desc; 728 | ``` 729 | 730 | 之后再`monitor.c`中修改相应部分即可: 731 | 732 | ``` 733 | int 734 | mon_backtrace(int argc, char **argv, struct Trapframe *tf) 735 | { 736 | struct Eipdebuginfo eipinfo; 737 | uintptr_t ebp, eip, args[5]; 738 | 739 | cprintf("Stack backtrace:\n"); 740 | 741 | for (ebp = read_ebp(); ebp != 0; ebp = *(uintptr_t *)ebp) { 742 | eip = *((uintptr_t *)ebp + 1); 743 | debuginfo_eip(eip, &eipinfo); 744 | args[0] = *((uintptr_t *)ebp + 2); 745 | args[1] = *((uintptr_t *)ebp + 3); 746 | args[2] = *((uintptr_t *)ebp + 4); 747 | args[3] = *((uintptr_t *)ebp + 5); 748 | args[4] = *((uintptr_t *)ebp + 6); 749 | cprintf(" ebp %08x eip %08x args %08x %08x %08x %08x %08x\n", 750 | ebp, eip, args[0], args[1], args[2], args[3], args[4]); 751 | cprintf(" %s:%d: %.*s+%d\n", 752 | eipinfo.eip_file, eipinfo.eip_line, eipinfo.eip_fn_namelen, 753 | eipinfo.eip_fn_name, eip - eipinfo.eip_fn_addr); 754 | } 755 | return 0; 756 | } 757 | ``` 758 | 759 | ## 遇到的困难以及解决方法 760 | 761 | 最开始是编译遇到了问题,之后参考[这里](https://bugs.launchpad.net/qemu/+bug/1193628),在`Makefile.target`中201行下面加入了一行`LIBS+=-lrt -lm`得以解决。 762 | 763 | 刚开始做Lab就在**Exercise 2**卡住了,虽然题目说了明白大概实在做什么就行,但是还是想去看一下具体在做什么。第一次看的时候读到开头转入保护模式的代码,就以为这就进入了Bootloader。但是越往后面执行越不对,而且地址也没有转到[0:0x7c00]。最后以比较大的跨度(`si 10000`)运行了半天,观察了指令的规律,并在网上查了很久的资料才明白这一大段都在做什么。 764 | 765 | **Exercise 2**之后的Exercise都比较顺利了,里面很多问题在之前其他课程的学习中也或多或少涉及过,并没有多大的困难。 766 | 767 | ## 收获及感想 768 | 769 | 这次lab虽然在实际需要写代码的内容不多,但内容含量还是很大的,花了意外多的时间。不过收货也很大。之前对操作系统启动的过程只有一个大致的概念,这次通过`gdb`的调试以及对相关源码的阅读,对booting的过程有了一个比较清晰的认识了。 770 | 771 | ## 对课程的意见和建议 772 | 773 | 这次lab虽然只有一周的时间,但是做起来还是挺费时间的,以后可以建议同学们早点动手,不要像我一样拖到最后一天。· 774 | 775 | ## 参考文献 776 | [1] [Bug #1193628 “Undefined References” : Bugs : QEMU](https://bugs.launchpad.net/qemu/+bug/1193628) 777 | [2] [Control register - Wikipedia, the free encyclopedia](http://en.wikipedia.org/wiki/Control_register) 778 | [3] [CMOS - OSDev Wiki](http://wiki.osdev.org/CMOS) 779 | [4] [Bochs Developers Guide](http://bochs.sourceforge.net/doc/docbook/development/cmos-map.html) 780 | [5] [A20 - a pain from the past](http://www.win.tue.nl/~aeb/linux/kbd/A20.html) 781 | [6] [Memory - Debugging with GDB](https://sourceware.org/gdb/current/onlinedocs/gdb/Memory.html#Memory) 782 | [7] [stdarg.h Source](http://research.microsoft.com/en-us/um/redmond/projects/invisible/include/stdarg.h.htm) from Microsoft 783 | [8] [Stab Section Basics - STABS](https://sourceware.org/gdb/onlinedocs/stabs/Stab-Section-Basics.html#Stab-Section-Basics) 784 | 785 | 以及其他Lab1中所提供的参考资料 -------------------------------------------------------------------------------- /boot/Makefrag: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile fragment for the JOS kernel. 3 | # This is NOT a complete makefile; 4 | # you must run GNU make in the top-level directory 5 | # where the GNUmakefile is located. 6 | # 7 | 8 | OBJDIRS += boot 9 | 10 | BOOT_OBJS := $(OBJDIR)/boot/boot.o $(OBJDIR)/boot/main.o 11 | 12 | $(OBJDIR)/boot/%.o: boot/%.c 13 | @echo + cc -Os $< 14 | @mkdir -p $(@D) 15 | $(V)$(CC) -nostdinc $(KERN_CFLAGS) -Os -c -o $@ $< 16 | 17 | $(OBJDIR)/boot/%.o: boot/%.S 18 | @echo + as $< 19 | @mkdir -p $(@D) 20 | $(V)$(CC) -nostdinc $(KERN_CFLAGS) -c -o $@ $< 21 | 22 | $(OBJDIR)/boot/main.o: boot/main.c 23 | @echo + cc -Os $< 24 | $(V)$(CC) -nostdinc $(KERN_CFLAGS) -Os -c -o $(OBJDIR)/boot/main.o boot/main.c 25 | 26 | $(OBJDIR)/boot/boot: $(BOOT_OBJS) 27 | @echo + ld boot/boot 28 | $(V)$(LD) $(LDFLAGS) -N -e start -Ttext 0x7C00 -o $@.out $^ 29 | $(V)$(OBJDUMP) -S $@.out >$@.asm 30 | $(V)$(OBJCOPY) -S -O binary -j .text $@.out $@ 31 | $(V)perl boot/sign.pl $(OBJDIR)/boot/boot 32 | 33 | -------------------------------------------------------------------------------- /boot/boot.S: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | # Start the CPU: switch to 32-bit protected mode, jump into C. 4 | # The BIOS loads this code from the first sector of the hard disk into 5 | # memory at physical address 0x7c00 and starts executing in real mode 6 | # with %cs=0 %ip=7c00. 7 | 8 | .set PROT_MODE_CSEG, 0x8 # kernel code segment selector 9 | .set PROT_MODE_DSEG, 0x10 # kernel data segment selector 10 | .set CR0_PE_ON, 0x1 # protected mode enable flag 11 | 12 | .globl start 13 | start: 14 | .code16 # Assemble for 16-bit mode 15 | cli # Disable interrupts 16 | cld # String operations increment 17 | 18 | # Set up the important data segment registers (DS, ES, SS). 19 | xorw %ax,%ax # Segment number zero 20 | movw %ax,%ds # -> Data Segment 21 | movw %ax,%es # -> Extra Segment 22 | movw %ax,%ss # -> Stack Segment 23 | 24 | # Enable A20: 25 | # For backwards compatibility with the earliest PCs, physical 26 | # address line 20 is tied low, so that addresses higher than 27 | # 1MB wrap around to zero by default. This code undoes this. 28 | seta20.1: 29 | inb $0x64,%al # Wait for not busy 30 | testb $0x2,%al 31 | jnz seta20.1 32 | 33 | movb $0xd1,%al # 0xd1 -> port 0x64 34 | outb %al,$0x64 35 | 36 | seta20.2: 37 | inb $0x64,%al # Wait for not busy 38 | testb $0x2,%al 39 | jnz seta20.2 40 | 41 | movb $0xdf,%al # 0xdf -> port 0x60 42 | outb %al,$0x60 43 | 44 | # Switch from real to protected mode, using a bootstrap GDT 45 | # and segment translation that makes virtual addresses 46 | # identical to their physical addresses, so that the 47 | # effective memory map does not change during the switch. 48 | lgdt gdtdesc 49 | movl %cr0, %eax 50 | orl $CR0_PE_ON, %eax 51 | movl %eax, %cr0 52 | 53 | # Jump to next instruction, but in 32-bit code segment. 54 | # Switches processor into 32-bit mode. 55 | ljmp $PROT_MODE_CSEG, $protcseg 56 | 57 | .code32 # Assemble for 32-bit mode 58 | protcseg: 59 | # Set up the protected-mode data segment registers 60 | movw $PROT_MODE_DSEG, %ax # Our data segment selector 61 | movw %ax, %ds # -> DS: Data Segment 62 | movw %ax, %es # -> ES: Extra Segment 63 | movw %ax, %fs # -> FS 64 | movw %ax, %gs # -> GS 65 | movw %ax, %ss # -> SS: Stack Segment 66 | 67 | # Set up the stack pointer and call into C. 68 | movl $start, %esp 69 | call bootmain 70 | 71 | # If bootmain returns (it shouldn't), loop. 72 | spin: 73 | jmp spin 74 | 75 | # Bootstrap GDT 76 | .p2align 2 # force 4 byte alignment 77 | gdt: 78 | SEG_NULL # null seg 79 | SEG(STA_X|STA_R, 0x0, 0xffffffff) # code seg 80 | SEG(STA_W, 0x0, 0xffffffff) # data seg 81 | 82 | gdtdesc: 83 | .word 0x17 # sizeof(gdt) - 1 84 | .long gdt # address gdt 85 | 86 | -------------------------------------------------------------------------------- /boot/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | /********************************************************************** 5 | * This a dirt simple boot loader, whose sole job is to boot 6 | * an ELF kernel image from the first IDE hard disk. 7 | * 8 | * DISK LAYOUT 9 | * * This program(boot.S and main.c) is the bootloader. It should 10 | * be stored in the first sector of the disk. 11 | * 12 | * * The 2nd sector onward holds the kernel image. 13 | * 14 | * * The kernel image must be in ELF format. 15 | * 16 | * BOOT UP STEPS 17 | * * when the CPU boots it loads the BIOS into memory and executes it 18 | * 19 | * * the BIOS intializes devices, sets of the interrupt routines, and 20 | * reads the first sector of the boot device(e.g., hard-drive) 21 | * into memory and jumps to it. 22 | * 23 | * * Assuming this boot loader is stored in the first sector of the 24 | * hard-drive, this code takes over... 25 | * 26 | * * control starts in boot.S -- which sets up protected mode, 27 | * and a stack so C code then run, then calls bootmain() 28 | * 29 | * * bootmain() in this file takes over, reads in the kernel and jumps to it. 30 | **********************************************************************/ 31 | 32 | #define SECTSIZE 512 33 | #define ELFHDR ((struct Elf *) 0x10000) // scratch space 34 | 35 | void readsect(void*, uint32_t); 36 | void readseg(uint32_t, uint32_t, uint32_t); 37 | 38 | void 39 | bootmain(void) 40 | { 41 | struct Proghdr *ph, *eph; 42 | 43 | // read 1st page off disk 44 | readseg((uint32_t) ELFHDR, SECTSIZE*8, 0); 45 | 46 | // is this a valid ELF? 47 | if (ELFHDR->e_magic != ELF_MAGIC) 48 | goto bad; 49 | 50 | // load each program segment (ignores ph flags) 51 | ph = (struct Proghdr *) ((uint8_t *) ELFHDR + ELFHDR->e_phoff); 52 | eph = ph + ELFHDR->e_phnum; 53 | for (; ph < eph; ph++) 54 | // p_pa is the load address of this segment (as well 55 | // as the physical address) 56 | readseg(ph->p_pa, ph->p_memsz, ph->p_offset); 57 | 58 | // call the entry point from the ELF header 59 | // note: does not return! 60 | ((void (*)(void)) (ELFHDR->e_entry))(); 61 | 62 | bad: 63 | outw(0x8A00, 0x8A00); 64 | outw(0x8A00, 0x8E00); 65 | while (1) 66 | /* do nothing */; 67 | } 68 | 69 | // Read 'count' bytes at 'offset' from kernel into physical address 'pa'. 70 | // Might copy more than asked 71 | void 72 | readseg(uint32_t pa, uint32_t count, uint32_t offset) 73 | { 74 | uint32_t end_pa; 75 | 76 | end_pa = pa + count; 77 | 78 | // round down to sector boundary 79 | pa &= ~(SECTSIZE - 1); 80 | 81 | // translate from bytes to sectors, and kernel starts at sector 1 82 | offset = (offset / SECTSIZE) + 1; 83 | 84 | // If this is too slow, we could read lots of sectors at a time. 85 | // We'd write more to memory than asked, but it doesn't matter -- 86 | // we load in increasing order. 87 | while (pa < end_pa) { 88 | // Since we haven't enabled paging yet and we're using 89 | // an identity segment mapping (see boot.S), we can 90 | // use physical addresses directly. This won't be the 91 | // case once JOS enables the MMU. 92 | readsect((uint8_t*) pa, offset); 93 | pa += SECTSIZE; 94 | offset++; 95 | } 96 | } 97 | 98 | void 99 | waitdisk(void) 100 | { 101 | // wait for disk reaady 102 | while ((inb(0x1F7) & 0xC0) != 0x40) 103 | /* do nothing */; 104 | } 105 | 106 | void 107 | readsect(void *dst, uint32_t offset) 108 | { 109 | // wait for disk to be ready 110 | waitdisk(); 111 | 112 | outb(0x1F2, 1); // count = 1 113 | outb(0x1F3, offset); 114 | outb(0x1F4, offset >> 8); 115 | outb(0x1F5, offset >> 16); 116 | outb(0x1F6, (offset >> 24) | 0xE0); 117 | outb(0x1F7, 0x20); // cmd 0x20 - read sectors 118 | 119 | // wait for disk to be ready 120 | waitdisk(); 121 | 122 | // read a sector 123 | insl(0x1F0, dst, SECTSIZE/4); 124 | } 125 | 126 | -------------------------------------------------------------------------------- /boot/sign.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | open(BB, $ARGV[0]) || die "open $ARGV[0]: $!"; 4 | 5 | binmode BB; 6 | my $buf; 7 | read(BB, $buf, 1000); 8 | $n = length($buf); 9 | 10 | if($n > 510){ 11 | print STDERR "boot block too large: $n bytes (max 510)\n"; 12 | exit 1; 13 | } 14 | 15 | print STDERR "boot block is $n bytes (max 510)\n"; 16 | 17 | $buf .= "\0" x (510-$n); 18 | $buf .= "\x55\xAA"; 19 | 20 | open(BB, ">$ARGV[0]") || die "open >$ARGV[0]: $!"; 21 | binmode BB; 22 | print BB $buf; 23 | close BB; 24 | -------------------------------------------------------------------------------- /conf/env.mk: -------------------------------------------------------------------------------- 1 | # env.mk - configuration variables for the JOS lab 2 | 3 | # '$(V)' controls whether the lab makefiles print verbose commands (the 4 | # actual shell commands run by Make), as well as the "overview" commands 5 | # (such as '+ cc lib/readline.c'). 6 | # 7 | # For overview commands only, the line should read 'V = @'. 8 | # For overview and verbose commands, the line should read 'V ='. 9 | V = @ 10 | 11 | # If your system-standard GNU toolchain is ELF-compatible, then comment 12 | # out the following line to use those tools (as opposed to the i386-jos-elf 13 | # tools that the 6.828 make system looks for by default). 14 | # 15 | # GCCPREFIX='' 16 | 17 | # If the makefile cannot find your QEMU binary, uncomment the 18 | # following line and set it to the full path to QEMU. 19 | # 20 | # QEMU= 21 | -------------------------------------------------------------------------------- /conf/lab.mk: -------------------------------------------------------------------------------- 1 | LAB=1 2 | PACKAGEDATE=Wed Sep 3 09:38:35 EDT 2014 3 | -------------------------------------------------------------------------------- /fs/testshell.key: -------------------------------------------------------------------------------- 1 | # echo hello world | cat 2 | hello world 3 | # cat lorem 4 | Lorem ipsum dolor sit amet, consectetur 5 | adipisicing elit, sed do eiusmod tempor 6 | incididunt ut labore et dolore magna 7 | aliqua. Ut enim ad minim veniam, quis 8 | nostrud exercitation ullamco laboris 9 | nisi ut aliquip ex ea commodo consequat. 10 | Duis aute irure dolor in reprehenderit 11 | in voluptate velit esse cillum dolore eu 12 | fugiat nulla pariatur. Excepteur sint 13 | occaecat cupidatat non proident, sunt in 14 | culpa qui officia deserunt mollit anim 15 | id est laborum. 16 | # cat lorem |num 17 | 1 Lorem ipsum dolor sit amet, consectetur 18 | 2 adipisicing elit, sed do eiusmod tempor 19 | 3 incididunt ut labore et dolore magna 20 | 4 aliqua. Ut enim ad minim veniam, quis 21 | 5 nostrud exercitation ullamco laboris 22 | 6 nisi ut aliquip ex ea commodo consequat. 23 | 7 Duis aute irure dolor in reprehenderit 24 | 8 in voluptate velit esse cillum dolore eu 25 | 9 fugiat nulla pariatur. Excepteur sint 26 | 10 occaecat cupidatat non proident, sunt in 27 | 11 culpa qui officia deserunt mollit anim 28 | 12 id est laborum. 29 | # cat lorem |num |num |num |num |num 30 | 1 1 1 1 1 Lorem ipsum dolor sit amet, consectetur 31 | 2 2 2 2 2 adipisicing elit, sed do eiusmod tempor 32 | 3 3 3 3 3 incididunt ut labore et dolore magna 33 | 4 4 4 4 4 aliqua. Ut enim ad minim veniam, quis 34 | 5 5 5 5 5 nostrud exercitation ullamco laboris 35 | 6 6 6 6 6 nisi ut aliquip ex ea commodo consequat. 36 | 7 7 7 7 7 Duis aute irure dolor in reprehenderit 37 | 8 8 8 8 8 in voluptate velit esse cillum dolore eu 38 | 9 9 9 9 9 fugiat nulla pariatur. Excepteur sint 39 | 10 10 10 10 10 occaecat cupidatat non proident, sunt in 40 | 11 11 11 11 11 culpa qui officia deserunt mollit anim 41 | 12 12 12 12 12 id est laborum. 42 | # lsfd -1 43 | fd 0: name testshell.sh isdir 0 size 113 dev file 44 | fd 1: name isdir 0 size 32 dev pipe 45 | fd 3: name isdir 0 size 32 dev pipe 46 | # cat script 47 | echo This is from the script. 48 | cat lorem | num | cat 49 | echo These are my file descriptors. 50 | lsfd -1 51 | echo This is the end of the script. 52 | # sh