* more re-work
[mascara-docs.git] / i386 / junos / standford / 2004 / labs / lab2.txt
blob9af2f8604018a925fc22c42d778b905048c76233
1 Advanced OS Lab 2 - Booting a PC
3 Introduction
5 This lab is split into three parts. The first part concentrates on getting
6 familiarized with x86 assembly language, the Bochs x86 emulator, and the PC's
7 power-on bootstrap procedure. The second part examines the boot loader for our
8 class kernel, which resides in the boot directory of the lab2 tree. Finally,
9 the third part delves into the initial template for our class kernel itself,
10 named JOS, which resides in the kernel directory.
12 Software Setup
14 The files you will need for this lab are available in ~class/src/lab2.tar.gz.
15 To install them in your account, do this:
17 % tar xzf ~class/lab2.tar.gz
18 % cd lab2
19 % setenv GCCPREFIX i386-jos-elf-
22 We have set up the appropriate compilers and simulators for you on the class
23 machines. If you are instead working on your own machine, the tools page has
24 directions on how to set up bochs and gcc for use with the lab.
26 Hand-In Procedure
28 When you are ready to hand in your lab, run gmake handin in the lab2 directory.
29 This will submit all the source code in your lab2 directory.
31 You do not need to turn in answers to any of the questions in the text of the
32 lab. (Do answer them for yourself though! They will help with the rest of the
33 lab.)
35 We will be grading your solutions with a grading program. You can run gmake
36 grade to test your solutions with the grading program.
38 Part 1: PC Bootstrap
40 The purpose of the first exercise is to introduce you to x86 assembly language
41 and the PC bootstrap process, and to get you started with the Bochs debugger.
42 You will not have to write any code for this part of the lab, but you should go
43 through it anyway for your own understanding and be prepared to answer the
44 questions posed below.
46 Getting Started with x86 assembly
48 If you are not already familiar with x86 assembly language, you will quickly
49 become familiar with it during this course! The PC Assembly Language Book is an
50 excellent place to start. Hopefully, the book contains mixture of new and old
51 material for you.
53 Warning: Unfortunately the examples in the book are written for the NASM
54 assembler, whereas we will be using the GNU assembler. NASM uses the so-called
55 Intel syntax while GNU uses the AT&T syntax. While semantically equivalent, an
56 assembly file will differ quite a lot, at least superficially, depending on
57 which syntax is used. Luckily the conversion between the two is pretty simple,
58 and is covered in Brennan's Guide to Inline Assembly.
59         ┌─────────────────────────────────────────────────────────────┐
60         │Exercise 1. Read or at least carefully scan the entire PC    │
61         │Assembly Language book, except that you should skip all      │
62         │sections after 1.3.5 in chapter 1, which talk about features │
63         │of the NASM assembler that do not apply directly to the GNU  │
64         │assembler. You may also skip chapters 5 and 6, and all       │
65         │sections under 7.2, which deal with processor and language   │
66         │features we won't use in class.                              │
67         │                                                             │
68         │Also read the section "The Syntax" in Brennan's Guide to     │
69         │familiarize yourself with the most important features of GNU │
70         │assembler syntax.                                            │
71         └─────────────────────────────────────────────────────────────┘
73 Certainly the definitive reference for x86 assembly language programming is
74 Intel's instruction set architecture reference, which you can find on the class
75 reference page in two flavors: an HTML edition of the old 80386 Programmer's
76 Reference Manual, which is much shorter and easier to navigate than more recent
77 manuals but describes all of the x86 processor features that we will make use
78 of in class; and the full, latest and greatest IA-32 Intel Architecture
79 Software Developer's Manuals from Intel, covering all the features of the most
80 recent processors that we won't need in class but you may be interested in
81 learning about. An equivalent (but even longer) set of manuals is available
82 from AMD, which also covers the new 64-bit extensions now appearing in both AMD
83 and Intel processors.
85 You should read the recommended chapters of the PC Assembly book, and "The
86 Syntax" section in Brennan's Guide now. Save the Intel/AMD architecture manuals
87 for later or use them for reference when you want to look up the definitive
88 explanation of a particular processor feature or instruction.
90 Simulating the x86
92 Instead of developing the operating system on a real, physical personal
93 computer (PC), we use a simulator, which emulates a complete PC faithfully
94 (i.e., the code you write for the simulator, boots on a real PC too). Using a
95 simulator simplifies debugging; you can, for example, set break points inside
96 of the simulated x86, which is difficult to do with the silicon-version of an
97 x86.
99 In class we will use the Bochs PC Emulator. This emulator has been around for
100 quite a while, and is slow and quirky but has a great many useful features.
101 Another freely available PC emulator is QEMU, which is much faster than Buchs
102 but has less mature debugging facilities. You are welcome to give QEMU a try
103 (or any of the commercially available PC virtual machine programs), but our
104 class lab assignments will assume (and sometimes require) that you are running
105 under Bochs.
107 To get started, extract the Lab 2 files into your own directory on a class
108 machine as described above in "Software Setup", then type gmake in the lab2
109 directory to build the minimal class boot loader and kernel you will start
110 with. (It's a little generous to call the code we're running here a "kernel,"
111 but we'll flesh it out throughout the semester.)
113 % cd lab2
114 % gmake
115 as kern/entry.S
116 cc kern/init.c
117 cc kern/console.c
118 cc kern/monitor.c
119 cc lib/printf.c
120 cc lib/printfmt.c
121 cc lib/readline.c
122 cc lib/string.c
123 ld obj/kern/kernel
124 as boot/boot.S
125 cc boot/main.c
126 mk boot/boot
127 boot block is 424 bytes (max 510)
128 mk obj/kern/bochs.img
131 Now you're ready to run Bochs. The necessary configuration file for Bochs,
132 named .bochsrc, is already supplied for you in the lab2 directory. This
133 .bochsrc includes a command to make Bochs use the file obj/kern/bochs.img,
134 created above, as the contents of the simulated PC's "virtual hard disk" once
135 Bochs starts running. This simulated hard disk image contains both our boot
136 loader (obj/boot/boot) and our kernel (obj/kern/kernel).
138 % bochs
139 ========================================================================
140                        Bochs x86 Emulator 2.1.1
141                            February 08, 2004
142 ========================================================================
143 00000000000i[     ] reading configuration from .bochsrc
144 ------------------------------
145 Bochs Configuration: Main Menu
146 ------------------------------
148 This is the Bochs Configuration Interface, where you can describe the
149 machine that you want to simulate.  Bochs has already searched for a
150 configuration file (typically called bochsrc.txt) and loaded it if it
151 could be found.  When you are satisfied with the configuration, go
152 ahead and start the simulation.
154 You can also start bochs with the -q option to skip these menus.
156 1. Restore factory default configuration
157 2. Read options from...
158 3. Edit options
159 4. Save options to...
160 5. Begin simulation
161 6. Quit now
163 Please choose one: [5]
165 Bochs has read the file .bochsrc describing the virtual x86 PC it will emulate
166 for our kernel, and it is stopping to give you an opportunity to change the
167 settings if desired before beginning the actual simulation. Since the
168 configuration settings are already correct, just press Enter to start the
169 simulation. (As Bochs points out, you can bypass this step in the future by
170 typing 'bochs -q' instead of just 'bochs'.)
172 Next at t=0
173 (0) [0x000ffff0] f000:fff0 (unk. ctxt): jmp f000:e05b             ; ea5be000f0
174 <bochs:1>
176 Bochs has now started the simulated machine, and is ready to execute the first
177 instruction. An X window should have popped up to show the "virtual display" of
178 the simulated PC. The window is blank because the simulated PC hasn't actually
179 booted yet - it's frozen in the state a real PC would be in just after being
180 turned on and coming out of hardware reset but before executing any
181 instructions.
183 The text that Bochs printed on your normal shell window, and the <bochs:1>
184 prompt, is part of the Bochs debugging interface, which you can use to control
185 and examine the state of the simulated PC. The main reference for this
186 debugging interface that you should become familiar with is the section Using
187 Bochs internal debugger in the Bochs User Manual. You can always get a reminder
188 of the names of the most common commands by typing help:
190 <bochs:1> help
191 help - show list of debugger commands
192 help 'command'- show short command description
193 -*- Debugger control -*-
194     help, q|quit|exit, set, instrument, show, trace-on, trace-off,
195     record, playback, load-symbols, slist
196 -*- Execution control -*-
197     c|cont, s|step|stepi, p|n|next, modebp
198 -*- Breakpoint management -*-
199     v|vbreak, lb|lbreak, pb|pbreak|b|break, sb, sba, blist,
200     bpe, bpd, d|del|delete
201 -*- CPU and memory contents -*-
202     x, xp, u|disas|disassemble, r|reg|registers, setpmem, crc, info, dump_cpu,
203     set_cpu, ptime, print-stack, watch, unwatch, ?|calc
205 For now, just type c to "continue" (i.e., start) execution from the current
206 point. Some text should now appear in the Bochs window, ending with:
208 Booting from Hard Disk...
209 480 decimal is XXX octal!
210 entering test_backtrace 5
211 entering test_backtrace 4
212 entering test_backtrace 3
213 entering test_backtrace 2
214 entering test_backtrace 1
215 entering test_backtrace 0
216 leaving test_backtrace 0
217 leaving test_backtrace 1
218 leaving test_backtrace 2
219 leaving test_backtrace 3
220 leaving test_backtrace 4
221 leaving test_backtrace 5
222 Welcome to the JOS kernel monitor!
223 Type 'help' for a list of commands.
226 Everything after 'Booting from Hard Disk...' was printed by our skeletal JOS
227 kernel; the K> is the prompt printed by the small monitor, or interactive
228 control program, that we've included in the kernel. These four lines printed by
229 the kernel will also appear in the regular shell window from which you ran
230 Bochs. This is because for testing and lab grading purposes we have set up the
231 JOS kernel to write its console output not only to the virtual VGA display (as
232 seen in the Bochs window), but also to the simulated PC's virtual parallel
233 port, which Bochs outputs to its own standard output because of a particular
234 line we included in our .bochsrc. (Identify that line!)
236 The kernel monitor is currently pretty boring; it only knows about two not
237 particularly useful commands:
239 K> help
240 help - display this list of commands
241 kerninfo - display information about the kernel
242 K> kerninfo
243 Special kernel symbols:
244   start  f0100020 (virt)  00100020 (phys)
245   etext  f01013a8 (virt)  001013a8 (phys)
246   edata  f010ac08 (virt)  0010ac08 (phys)
247   end    f010b268 (virt)  0010b268 (phys)
248 Kernel executable memory footprint: 45KB
251 The help command is obvious, and we will shortly discuss the meaning of what
252 the kerninfo command prints. Although simple, it's important to note that this
253 kernel monitor is running "directly" on the "raw (virtual) hardware" of the
254 simulated PC. This means that you should be able to copy the contents of obj/
255 kern/bochs.img onto the first few sectors of a real hard disk, insert that hard
256 disk into a real PC, turn it on, and see exactly the same thing on the PC's
257 real screen as you did above in the Bochs window. (We don't recommend you do
258 this on a real machine with useful information on its hard disk, though,
259 because copying bochs.img onto the beginning of its hard disk will trash the
260 master boot record and the beginning of the first partition, effectively
261 causing everything previously on the hard disk to be lost!)
262         ┌─────────────────────────────────────────────────────────────┐
263         │Exercise 2. Scan through the Bochs internal debugger section │
264         │of the Bochs user manual to get a feel for these commands and│
265         │their syntax. Play with the commands a little: do some       │
266         │stepping and tracing through the code, examining CPU         │
267         │registers and memory and disassembling instructions at       │
268         │different points, without worrying too much yet about what   │
269         │the code is actually doing. While the kernel monitor is      │
270         │waiting for user input (or at any other time the simulation  │
271         │is running), you can always hit CTRL-C in the shell window   │
272         │from which you ran Bochs in order to halt the simulation and │
273         │break back into the Bochs debugger. Be sure you understand   │
274         │the distinction between which software you're interacting    │
275         │with when you type commands in the kernel monitor versus in  │
276         │the Bochs debugger.                                          │
277         └─────────────────────────────────────────────────────────────┘
279 The PC's Physical Address Space
281 We will now dive into a bit more detail about how a PC starts up. A PC's
282 physical address space is hard-wired to have the following general layout:
284 +------------------+  <- 0xFFFFFFFF (4GB)
285 |      32-bit      |
286 |  memory mapped   |
287 |     devices      |
288 |                  |
289 /\/\/\/\/\/\/\/\/\/\
291 /\/\/\/\/\/\/\/\/\/\
292 |                  |
293 |      Unused      |
294 |                  |
295 +------------------+  <- depends on amount of RAM
296 |                  |
297 |                  |
298 | Extended Memory  |
299 |                  |
300 |                  |
301 +------------------+  <- 0x00100000 (1MB)
302 |     BIOS ROM     |
303 +------------------+  <- 0x000F0000 (960KB)
304 |  16-bit devices, |
305 |  expansion ROMs  |
306 +------------------+  <- 0x000C0000 (768KB)
307 |   VGA Display    |
308 +------------------+  <- 0x000A0000 (640KB)
309 |                  |
310 |    Low Memory    |
311 |                  |
312 +------------------+  <- 0x00000000
314 The first PCs, which were based on the 16-bit Intel 8088 processor, were only
315 capable of addressing 1MB of physical memory. The physical address space of an
316 early PC would therefore start at 0x00000000 but end at 0x000FFFFF instead of
317 0xFFFFFFFF. The 640KB area marked "Low Memory" was the only random-access
318 memory (RAM) that an early PC could use; in fact the very earliest PCs only
319 could be configured with 16KB, 32KB, or 64KB of RAM!
321 The 384KB area from 0x000A0000 through 0x000FFFFF was reserved by the hardware
322 for special uses such as video display buffers and firmware held in nonvolatile
323 memory. The most important part of this reserved area is the Basic Input/Output
324 System (BIOS), which occupies the 64KB region from 0x000F0000 through
325 0x000FFFFF. In early PCs the BIOS was held in true read-only memory (ROM), but
326 current PCs store the BIOS in updateable flash memory. The BIOS is responsible
327 for performing basic system initialization such as activating the video card
328 and checking the amount of memory installed. After performing this
329 initialization, the BIOS loads the operating system from some appropriate
330 location such as floppy disk, hard disk, CD-ROM, or the network, and passes
331 control of the machine to the operating system.
333 When Intel finally "broke the one megabyte barrier" with the 80286 and 80386
334 processors, which supported 16MB and 4GB physical address spaces respectively,
335 the PC architects nevertheless preserved the original layout for the low 1MB of
336 physical address space in order to ensure backward compatibility with existing
337 software. Modern PCs therefore have a "hole" in physical memory from 0x000A0000
338 and 0x00100000, dividing RAM into "low" or "conventional memory" (the first
339 640KB) and "extended memory" (everything else). In addition, some space at the
340 the very top of the PC's 32-bit physical address space, above all physical RAM,
341 is now commonly reserved by the BIOS for use by 32-bit PCI devices.
343 With recent x86 processors it is now possible in fact for PCs to have more than
344 4GB of physical RAM, which means that RAM can extend further above 0XFFFFFFFF.
345 In this case the BIOS must therefore arrange to leave a second hole in the
346 system's RAM at the top of the 32-bit addressable region, in order to leave
347 room for these 32-bit devices to be mapped. Because of design limitations JOS
348 will actually be limited to using only the first 256MB of a PC's physical
349 memory anyway, so for now we will pretend that all PCs still have "only" a
350 32-bit physical address space. But dealing with complicated physical address
351 spaces and other aspects of hardware organization that evolved over many years
352 is one of the important practical challenges of OS development.
354 The ROM BIOS
356 Close and restart Bochs, so that you once again see the first instruction to be
357 executed:
359 Next at t=0
360 (0) [0x000ffff0] f000:fff0 (unk. ctxt): jmp f000:e05b             ; ea5be000f0
361 <bochs:1>
363 From this output you can conclude a few things:
365   • The IBM PC starts executing at physical address 0x000ffff0, which is at the
366     very top of the 64KB area reserved for the ROM BIOS. You know that this is
367     the first instruction because of the message "Next at t=0" ("time 0").
368   • The PC starts executing in real mode, with CS = 0xf000 and IP = 0xfff0. The
369     f000:fff0 is the segmented address that translates to 0x000ffff0 in real
370     mode.
371   • The first instruction to be executed is a jmp instruction, which jumps to
372     the real mode segmented address CS = 0xf000 and IP = 0xe05b.
374 Why does Bochs start like this? This is how Intel designed the 8088 processor,
375 which IBM used in their original PC. Because the BIOS in a PC is "hard-wired"
376 to the physical address range 0x000f0000-0x000fffff, this design ensures that
377 the BIOS always gets control of the machine first after power-up or any system
378 restart - which is crucial because on power-up there is no other software
379 anywhere in the machine's RAM that the processor could execute. The Bochs
380 simulator comes with its own BIOS, which it maps at this location in the
381 processor's simulated physical address space. On processor reset, the
382 (simulated) processor sets CS to 0xf000 and the IP to 0xfff0, and consequently,
383 execution begins at that (CS:IP) segment address. But how did the segmented
384 address 0xf000:fff0 turn into the physical 0x000ffff0 we mentioned above?
386 To answer that we need to know a bit about real mode addressing. In real mode
387 (the mode that PC starts off in), address translation works according to the
388 formula: physical address = 16 * segment + offset. So, when the PC sets CS to
389 0xf000 and IP to 0xfff0, the physical address referenced is:
391    16 * 0xf000 + 0xfff0   # in hex multiplication by 16 is
392    = 0xf0000 + 0xfff0     # easy--just append a 0.
393    = 0xffff0
395 We can see that the PC starts executing 16 bytes from the end of the BIOS code.
396 Therefore we shouldn't be surprised that the first thing that the BIOS does is
397 jmp backwards to an earlier location in the BIOS; after all how much could it
398 accomplish in just 16 bytes?
400         ┌─────────────────────────────────────────────────────────────┐
401         │Exercise 3. Use the Bochs debugger to trace into the ROM BIOS│
402         │for a few more instructions, and try to guess what it might  │
403         │be doing. You might find the common I/O address assignments  │
404         │table in Phil Storrs PC Hardware book handy, as well as other│
405         │materials on the class reference materials page. No need to  │
406         │figure out all the details - just the general idea of what   │
407         │the BIOS is doing first.                                     │
408         └─────────────────────────────────────────────────────────────┘
410 When the BIOS runs, it sets up an interrupt descriptor table and initializes
411 various devices such as the VGA display. This is where the "Plex86/Bochs
412 VGABios" messages you see in the Bochs window come from. How can you find out
413 exactly where in the BIOS this is happening? It happens that while in text
414 mode, the VGA display is mapped in memory at address 0xb8000, and you can use a
415 data watchpoint, or a breakpoint that fires when a particular memory location
416 is read or written (instead of executed), to find out when and where the BIOS
417 is writing these messages to the display.
419 To set a data watchpoint:
421 <bochs:1> watch write 0xb8000
422 <bochs:2> watch stop
423 <bochs:3> c
425 The first line sets the watchpoint. The second line instructs bochs to stop the
426 simulation whenever a watchpoint fires.
428         ┌─────────────────────────────────────────────────────────────┐
429         │Exercise 4. Set a watchpoint in the video display memory as  │
430         │instructed above. How many times is the watchpoint hit       │
431         │between when Bochs starts running and when the BIOS transfers│
432         │control to the boot loader? Is it the "main" ROM BIOS in the │
433         │0x000f0000-0x000fffff region that is performing this task, or│
434         │something else? Think about why the PC is designed this way. │
435         └─────────────────────────────────────────────────────────────┘
437 After initializing the PCI bus and all the important devices the BIOS knows
438 about, it searches for a bootable device such as a floppy, hard drive, or
439 CD-ROM. Eventually, when it finds a bootable disk, the BIOS reads the boot
440 loader from the disk and transfers control to it.
442 Part 2: The Boot Loader
444 Floppy and hard disks for PCs are by historical convention divided up into 512
445 byte regions called sectors. A sector is the disk's minimum transfer
446 granularity: each read or write operation must be one or more sectors in size
447 and aligned on a sector boundary. If the disk is bootable, the first sector is
448 called the boot sector, since this is where the boot loader code resides. When
449 the BIOS finds a bootable floppy or hard disk, it loads the 512-byte boot
450 sector into memory at physical addresses 0x7c00 through 0x7dff, and then uses a
451 jmp instruction to set the CS:IP to 0000:7c00, passing control to the boot
452 loader. Like the BIOS load address, these addresses are fairly arbitrary - but
453 they are fixed and standardized for PCs.
455 The ability to boot from a CD-ROM came much later during the evolution of the
456 PC, and as a result the PC architects took the opportunity to rethink the boot
457 process slightly. As a result, the way a modern BIOS boots from a CD-ROM is a
458 bit more complicated (and more powerful). CD-ROMs use a sector size of 2048
459 bytes instead of 512, and the BIOS can load a much larger boot image from the
460 disk into memory (not just one sector) before transferring control to it. For
461 more information, see the "El Torito" Bootable CD-ROM Format Specification.
463 For class, however, we will use the conventional hard drive boot mechanism,
464 which means that our boot loader must fit into a measly 512 bytes. The boot
465 loader consists of one assembly language source file, boot/boot.S, and one C
466 source file, boot/main.c Look through these source files carefully and make
467 sure you understand what's going on. The boot loader must perform two main
468 functions:
470  1. First, the boot loader switches the processor from real mode to 32-bit
471     protected mode, because it is only in this mode that software can access
472     all the memory above 1MB in the processor's physical address space.
473     Protected mode is described briefly in sections 1.2.7 and 1.2.8 of PC
474     Assembly Language, and in great detail in the Intel architecture manuals.
475     At this point you only have to understand that translation of segmented
476     addresses (segment:offset pairs) into physical addresses happens
477     differently in protected mode, and that after the transition offsets are 32
478     bits instead of just 16.
479  2. Second, the boot loader reads the kernel from the hard disk by directly
480     accessing the IDE disk device registers via the x86's special I/O
481     instructions. If you would like to understand better what the particular I/
482     O instructions here actually mean, check out the "IDE hard drive
483     controller" section on the class reference page. You will not need to learn
484     much about programming specific devices in this class: writing device
485     drivers is in practice a very important part of OS development, but from a
486     conceptual or architectural viewpoint it is also one of the least
487     interesting.
489 After you understand the boot loader source code, look at the file obj/boot/
490 boot.asm. This file is a disassembly of the boot loader that our GNUmakefile
491 creates after compiling the boot loader. This disassembly file makes it easy to
492 see exactly where in physical memory all of the boot loader's code resides, and
493 makes it easier to track what's happening while stepping through the boot
494 loader in Bochs.
496 You can set breakpoints in Bochs with the b command. You have to give the base
497 explicitly, so say something like b 0x7c00 for hexadecimal. A full command
498 overview is at http://bochs.sourceforge.net/doc/docbook/user/x2461.html. From
499 the debugger, you can continue execution using the c and s commands: c causes
500 Bochs to continue execution indefinitely; and s allows you to step through the
501 instructions, executing exactly n instructions (a default of 1 if the parameter
502 n is not specified) before suspending execution again. trace-on and trace-off
503 can be used to set tracing before using the other commands.
505 To examine instructions in memory (besides the immediate next one to be
506 executed, which Bochs prints automatically), you use the u command. This
507 command has the syntax u/n addr, where n is the number of consecutive
508 instructions to disassemble and addr is the memory address at which to start
509 disassembling.
511         ┌─────────────────────────────────────────────────────────────┐
512         │Exercise 5. Set a breakpoint at address 0x7c00, which is     │
513         │where the boot sector will be loaded. Continue execution     │
514         │until that break point. Trace through the code in boot/      │
515         │boot.S, using the source code and the disassembly file obj/  │
516         │boot/boot.asm to keep track of where you are. Also use the u │
517         │command in Bochs to disassemble sequences of instructions in │
518         │the boot loader, and compare the original boot loader source │
519         │code with both the GNU disassembly in obj/boot/boot.asm and  │
520         │the Bochs disassembly from the u command.                    │
521         │                                                             │
522         │Trace into cmain() in boot/main.c, and then into read_sector │
523         │(). Identify the exact assembly instructions that correspond │
524         │each of the statements in read_sector(). Trace through the   │
525         │rest of read_sector() and back out into cmain(), and identify│
526         │the begin and end of the for loop that reads the remaining   │
527         │sectors of the kernel from the disk. Find out what code will │
528         │run when the loop is finished, set a breakpoint there, and   │
529         │continue to that breakpoint. Then step through the remainder │
530         │of the boot loader.                                          │
531         └─────────────────────────────────────────────────────────────┘
533 Be able to answer the following questions:
535   • At exactly what point does the processor transition from executing 16-bit
536     code to executing 32-bit code?
537   • What is the last instruction of the boot loader executed, and what is the
538     first instruction of the kernel it just loaded?
539   • How does the boot loader decide how many sectors it must read in order to
540     fetch the entire kernel from disk? Where does it find this information?
542         ┌─────────────────────────────────────────────────────────────┐
543         │Challenge! Make JOS boot under Bochs from a simulated CD-ROM.│
544         │You will need to learn about the mkisofs utility and will    │
545         │have to modify the .bochsrc appropriately.                   │
546         └─────────────────────────────────────────────────────────────┘
548 Loading the Kernel
550 We will now look in further detail at the C language portion of the boot
551 loader, in boot/main.c. To make sense out of boot/main.c you'll need to know
552 what an ELF binary is. When you compile and link a C program such as the JOS
553 kernel, the compiler transforms each C source ('.c') file into an object ('.o')
554 file containing assembly language instructions encoded in the compact binary
555 format expected by the hardware. The linker then combines all of the compiled
556 object files into a single binary image such as obj/kern/kernel, which in this
557 case is a binary in the ELF format, which stands for "Executable and Linkable
558 Format".
560 Full information about this format is available in the ELF specification on our
561 reference page, but you will not need to delve very deeply into the details of
562 this format in this class. Although as a whole the format is quite powerful and
563 complex, most of the complex parts are for supporting dynamic loading of shared
564 libraries, which we will not do in this class.
566 For purposes of this class, you can consider the contents of an ELF executable
567 to be (mostly) just a short, fixed-length header with important loading
568 information, followed by several program sections, which are just contiguous
569 chunks of code or data intended to be loaded byte-for-byte into memory at a
570 fixed, pre-computed address before transferring control to the program. The
571 loader does nothing to the code or data at load time; it must be ready to go.
573 An ELF binary starts with a fixed-length ELF header, followed by a
574 variable-length program header listing each of the program sections to be
575 loaded. The C definitions for these ELF headers are located in inc/elf.h. The
576 relevant sections for our purposes are named as follows:
578   • '.text': The text section holds the program's machine code.
579   • '.rodata': Data that is intended to be read-only, such as ASCII string
580     constants produced by the C compiler. (We will not actually bother making
581     this data read-only, however.)
582   • '.data': The data section holds the program's general (initialized) data,
583     such as global variables that are declared in the program with initializers
584     such as 'int x = 5;'.
586 These conventional section names obviously reflect the processor's viewpoint:
587 anything that humans would consider "text", such as ASCII strings generated by
588 the C compiler from string literals in the source code, will actually be found
589 in the '.rodata' section.
591 While the linker is computing the memory layout of a program, it reserves
592 memory space for all uninitialized global variables, such as just 'int x;', in
593 yet another, special section called '.bss' that immediately follows the data
594 section in memory. Since this section is supposed to be "uninitialized",
595 however - or rather, initialized to a default value of all zeros, as required
596 for all global variables in C - there is no need for the contents of this
597 section to be stored in the ELF binary file. Instead, the linker simply records
598 the address and size of the bss section in the ELF program header along with
599 the sizes of the other sections to be loaded, and leaves it to the loader (or
600 in some cases the program itself) to zero the bss section.
602 You can display a full list of the names, sizes, and link addresses of all the
603 sections in the kernel executable by typing:
605 % i386-jos-elf-objdump -h obj/kern/kernel
607 obj/kern/kernel:     file format elf32-i386
609 Sections:
610 Idx Name          Size      VMA       LMA       File off  Algn
611   0 .text         0000105e  f0100020  f0100020  00001020  2**2
612                   CONTENTS, ALLOC, LOAD, READONLY, CODE
613   1 .rodata       00000443  f0101080  f0101080  00002080  2**2
614                   CONTENTS, ALLOC, LOAD, READONLY, DATA
615   2 .data         00008b80  f0102000  f0102000  00003000  2**12
616                   CONTENTS, ALLOC, LOAD, DATA
620 You will see many more sections than the ones we listed above, but the others
621 are not important for our purposes. Most of the others are to hold debugging
622 information, which is typically included in the program's executable file but
623 not actually loaded into memory by the program loader.
625 Besides the section information, there is one more field in the ELF header that
626 is important to us, named e_entry. This field holds the link address of the
627 entry point in the program: the memory address in the program's text section at
628 which the program is supposed to begin executing.
630 To examine memory in the Bochs simulator, you use the x command, which has the
631 same syntax as gdb's. The command overview (linked above) has full details. For
632 now, it is enough to know that the recipe x/nx addr prints n words of memory at
633 addr. (Note that both 'x's in the command are lowercase.)
635 Warning: The size of a word is not a universal standard. To Bochs, a word is
636 four bytes. In GNU assembly, a word is two bytes (the 'w' in xorw, which stands
637 for word, means 2 bytes).
639         ┌─────────────────────────────────────────────────────────────┐
640         │Exercise 6. Reset the machine (exit bochs and start it       │
641         │again). Examine the 8 words of memory at 0x00100000 at the   │
642         │point the BIOS enters the boot loader, and then again at the │
643         │point the boot loader enters the kernel. Why are they        │
644         │different? What is there at the second breakpoint? (You do   │
645         │not really need to use Bochs to answer this question. Just   │
646         │think.)                                                      │
647         └─────────────────────────────────────────────────────────────┘
649 Link vs. Load Address
651 The load address of a binary is the memory address at which a binary is
652 actually loaded. For example, the BIOS is loaded by the PC hardware at address
653 0xf0000. So this is the BIOS's load address. Similarly, the BIOS loads the boot
654 sector at address 0x7c00. So this is the boot sector's load address.
656 The link address of a binary is the memory address for which the binary is
657 linked. Linking a binary for a given link address prepares it to be loaded at
658 that address. A program's link address in practice becomes subtly encoded
659 within the binary in a multitude of ways, with the result that if a binary is
660 not loaded at the address that it is linked for, things will not work.
662 In one sentence: the link address is the location where a binary assumes it is
663 going to be loaded, while the load address is the location where a binary
664 actually is loaded. It's up to us to make sure that they turn out to be the
665 same.
667 Look at the linker commands in boot/Makefrag and kern/Makefrag used to build
668 our boot loader and kernel, and you will find a (different) -Ttext option in
669 each case, specifying the link address for the binary.
671         ┌─────────────────────────────────────────────────────────────┐
672         │Exercise 7. Trace through the first few instructions of the  │
673         │boot loader again and identify the first instruction that    │
674         │would "break" or otherwise do the wrong thing if you were to │
675         │get the boot loader's link address wrong. Then change the    │
676         │link address in boot/Makefrag to something wrong, recompile  │
677         │the boot loader, and trace into it again to see what happens.│
678         │Don't forget to change it back afterwards!                   │
679         └─────────────────────────────────────────────────────────────┘
681 When object code contains no absolute addresses that ``subtly encode'' the link
682 address in this fashion, we say that the code is position-independent: it will
683 behave correctly no matter where it is loaded. GCC can generate
684 position-independent code using the -fpic option, and this feature is used
685 extensively in modern shared libraries that use the ELF executable format.
686 Position independence typically has some performance cost, however, because it
687 restricts the ways in which the compiler may choose instructions to access the
688 program's data. We will not use position-independent code at all in 6.828,
689 simply because we have no pressing need to.
691 Part 3: The Kernel
693 We will now start to examine the minimal JOS kernel in a bit more detail. (And
694 you will finally get to write some code!) Like the boot loader, the kernel
695 begins with some assembly language code that sets things up so that C language
696 code can execute properly.
698 Using segmentation to work around position dependence
700 Did you notice above that while the boot loader's link and load addresses match
701 perfectly, there appears to be a (rather large) disparity between the kernel's
702 link and load addresses? Go back and check both and make sure you can see what
703 we're talking about.
705 Operating system kernels often like to be linked and run at very high virtual
706 address, such as 0xf0100020, in order to leave the lower part of the
707 processor's virtual address space for user programs to use. The reason for this
708 arrangement will become clearer in the next lab. Most machines don't even have
709 that much physical memory, however. (How much would it be exactly?)
711 Since we can't actually load the kernel at physical address 0xf0100020, we will
712 use the processor's memory management hardware to map virtual address
713 0xf0100020 - the link address at which the kernel code expects to run - to
714 physical address 0x00100020 - where the boot loader actually loaded the kernel.
715 This way, although the kernel's virtual address is high enough to leave plenty
716 of address space for user processes, it will be loaded in physical memory at
717 the 1MB point in the PC's RAM, just above the BIOS ROM.
719 In fact, we will actually map the entire bottom 256MB of the PC's physical
720 address space, from 0x00000000 through 0x0fffffff, to virtual addresses
721 0xf0000000 through 0xffffffff respectively. You should now be able to see why
722 the JOS kernel is limited to using only the first 256MB of physical memory in a
725 The x86 processor has two distinct memory management mechanisms that we could
726 use to achieve this mapping: segmentation and paging. Both are described in
727 full detail in the 80386 Programmer's Reference Manual or the IA-32 Developer's
728 Manual, Volume 3. When the JOS kernel first starts up, we'll initially use
729 segmentation to establish our desired virtual-to-physical mapping, because it
730 is quick and easy - and the x86 processor requires us to set up the
731 segmentation hardware in any case, because it can't be disabled!
733         ┌─────────────────────────────────────────────────────────────┐
734         │Exercise 8. Use Bochs to trace into the JOS kernel, and      │
735         │identify the exact point at which the new virtual-to-physical│
736         │mapping takes effect. Then examine the Global Descriptor     │
737         │Table (GDT) that the code uses to achieve this effect, and   │
738         │make sure you understand what's going on.                    │
739         │                                                             │
740         │What is the first instruction after this point that would    │
741         │fail to work properly if this virtual-to-physical mapping    │
742         │wasn't in place? Comment out or otherwise intentionally      │
743         │"break" the segmentation setup code in kern/entry.S, trace   │
744         │into it in Bochs, and see if you were right.                 │
745         └─────────────────────────────────────────────────────────────┘
747 Formatted Printing to the Console
749 Most people take functions like printf() for granted, sometimes even thinking
750 of them as "primitives" of the C language. But in an OS kernel, all I/O of any
751 kind that we do, we have to implement ourselves!
753 Read through lib/printf.c, lib/printfmt.c, and kern/console.c, and make sure
754 you understand their relationship. It will become clear in later labs why the
755 first two source files are located in the separate lib directory.
757         ┌─────────────────────────────────────────────────────────────┐
758         │Exercise 9. We have omitted a small fragment of code - the   │
759         │code necessary to print octal numbers using patterns of the  │
760         │form "%o". Find and fill in this code fragment.              │
761         └─────────────────────────────────────────────────────────────┘
763 Be able to answer the following questions:
765  1. Explain the interface between printf.c and console.c. Specifically, what
766     function does console.c export? How is this function used by printf.c?
767  2. Explain the following from console.c:
769     1       if (crt_pos >= CRT_SIZE) {
770     2               int i;
771     3               memcpy(crt_buf, crt_buf + CRT_COLS, CRT_SIZE << 1);
772     4               for (i = CRT_SIZE - CRT_COLS; i < CRT_SIZE; i++)
773     5                       crt_buf[i] = 0x0700 | ' ';
774     6               crt_pos -= CRT_COLS;
775     7       }
777  3. For the following questions you might wish to consult the notes for Lecture
778     2. These notes cover GCC's calling convention on the x86.
780     Trace the execution of the following code step-by-step:
782     int x = 1, y = 3, z = 4;
783     warn("x %d, y %x, z %d\n", x, y, z);
785       □ First, in the call to printf(), to what does fmt point? To what does ap
786         point?
787       □ Second, list (in order of execution) each call to cons_putc, va_arg,
788         and vprintf. For cons_putc, list its argument as well. For va_arg, list
789         what ap points to before and after the call. For vprintf list the
790         values of its two arguments.
791  4. Run the following code.
793         u_int i = 0x00646c72;
794         warn("H%x Wo%s", 57616, &i);
796     What is the output? Explain how this output is arrived out in the
797     step-by-step manner of the previous exercise.
799     The output depends on that fact that the x86 is little-endian. If the x86
800     were instead big-endian what would you set i to in order to yield the same
801     output? Would you need to change 57616 to a different value?
803     Here's a description of little- and big-endian and a more whimsical
804     description.
805  5. In the following code, what is going to be printed after 'y='? (note: the
806     answer is not a specific value.) Why does this happen?
808         warn("x=%d y=%d", 3);
810  6. Let's say that GCC changed its calling convention so that it passed
811     arguments in declaration order (i.e., the opposite of reverse order). How
812     would you have to change printf or its interface so that it would still be
813     possible to pass it a variable number of arguments?
815         ┌─────────────────────────────────────────────────────────────┐
816         │Challenge! Enhance the console to allow text to be printed in│
817         │different colors. The traditional way to do this would be to │
818         │make it interpret ANSI escape sequences embedded in the text │
819         │strings printed to the console, but you may use any mechanism│
820         │you like. There is plenty of information on the references   │
821         │page and elsewhere on the web on programming the VGA display │
822         │hardware. If you're feeling really adventurous, you could try│
823         │switching the VGA hardware into a graphics mode and making   │
824         │the console draw text onto the graphical frame buffer.       │
825         └─────────────────────────────────────────────────────────────┘
827 The Stack
829 In the final exercise of this lab, we will explore in more detail the way the C
830 language uses the stack on the x86, and in the process write a useful new
831 kernel monitor function that prints a backtrace of the stack: a list of the
832 saved Instruction Pointer (IP) values that can be used to determine the exact
833 context in which a particular piece of C code was called.
835         ┌─────────────────────────────────────────────────────────────┐
836         │Exercise 10. Determine where the kernel initializes its      │
837         │stack, and exactly where in memory its stack is located. How │
838         │does the kernel reserve space for its stack? And at which    │
839         │"end" of this reserved area is the stack pointer initialized │
840         │to point to?                                                 │
841         └─────────────────────────────────────────────────────────────┘
843 In C programs on the x86, both the esp (stack pointer) and ebp (base pointer)
844 registers typically have special meanings. The stack pointer points to the
845 current dividing point in the stack area between "free" stack space and "in
846 use" stack space. Since the stack grows down on the x86 (and, historically,
847 most other processors, with a few exceptions), at a given time the stack
848 pointer points to the first "in use" byte of the stack; everything below that
849 location in the region reserved for the stack is considered "free". Pushing
850 values onto the stack decreases the stack pointer, and popping values off the
851 stack increases the stack pointer. Various x86 processor instructions, such as
852 call are "hard-wired" to use the stack pointer register in specific ways.
854 The ebp, in contrast, is associated with the stack primarily by software
855 convention. On entry to a C function, the function's prologue code normally
856 saves the previous function's base pointer by pushing it onto the stack, and
857 then copies the current esp value into ebp for the duration of the function. If
858 all the functions in a program consistently obey this convention, then at any
859 given point during the program's execution, it is possible to trace back
860 through the stack by following the chain of saved ebp pointers and determining
861 exactly what nested sequence of function calls caused this particular point in
862 the program to be reached. This capability can be particularly useful, for
863 example, when a particular function causes an assert failure or panic because
864 bad arguments were passed to it, but you aren't sure who passed the bad
865 arguments. With stack backtrace functionality, you can trace back and find the
866 offending function.
867         ┌─────────────────────────────────────────────────────────────┐
868         │Exercise 11. To become familiar with the C calling           │
869         │conventions on the x86, find the address of the              │
870         │test_backtrace function in obj/kern/kernel.asm, set a        │
871         │breakpoint there in Bochs, and examine what happens each time│
872         │it gets called after the kernel starts. How many 32-bit words│
873         │does each recursive nesting level of test_backtrace push on  │
874         │the stack, and what are those words?                         │
875         └─────────────────────────────────────────────────────────────┘
877 The above exercise should give you the information you need to implement a
878 stack backtrace function, which you should call mon_backtrace(). A prototype
879 for this function is already waiting for you in kern/monitor.c. You can do it
880 entirely in C, but you may find the read_ebp() function in inc/x86.h useful.
881 You'll also have to hook this new function into the kernel monitor's command
882 list so that it can actually be invoked interactively by the user.
884 The backtrace function should display a listing of function call frames in the
885 following format:
887 Stack backtrace:
888   ebp f0109e58  eip f0100a62  args 00000001 f0109e80 f0109e98 f0100ed2 00000031
889   ebp f0109ed8  eip f01000d6  args 00000000 00000000 f0100058 f0109f28 00000061
890   ...
892 The first line printed reflects the currently executing function, namely
893 mon_backtrace itself, the second line reflects the function that called
894 mon_backtrace, the third line reflects the function that called that one, and
895 so on. You should print all the outstanding stack frames, not just a specific
896 number for example. By studying kern/entry.S you'll find that there is an easy
897 way to tell when to stop.
899 Within each line, the ebp value indicates the base pointer into the stack used
900 by that function: i.e., the position of the stack pointer just after the
901 function was entered and the function prologue code set up the base pointer.
902 The listed eip value is the function's return instruction pointer: the
903 instruction address (hopefully in the kernel's text section) where control will
904 return to when the function returns. This address typically indicates exactly
905 the point from which the function in question was called. (More accurately, the
906 return eip usually points to the instruction immediately following the relevant
907 call instruction - why?) Finally, the five hex values listed after args are the
908 first five arguments to the function in question, which would have been pushed
909 on the stack just before the function was called. If the function was called
910 with fewer than five arguments, of course, then not all five of these values
911 will be useful. (Why can't the backtrace code detect how many arguments there
912 actually are? How could this limitation be fixed?)
913         ┌─────────────────────────────────────────────────────────────┐
914         │Exercise 12. Implement the backtrace function as specified   │
915         │above. When you think you have it working right, run gmake   │
916         │grade to see if its output conforms to what our grading      │
917         │script expects, and fix it if it doesn't. After you have     │
918         │handed in your Lab 2 code, you are welcome to change the     │
919         │output format of the backtrace function any way you like.    │
920         └─────────────────────────────────────────────────────────────┘
921         ┌─────────────────────────────────────────────────────────────┐
922         │Challenge! Modify the JOS boot loader to load the symbol     │
923         │table from the kernel's ELF binary in addition to the text   │
924         │and data sections, and then modify your stack backtrace      │
925         │function to look up and display the name of each function    │
926         │called in the backtrace in addition to the return eip. You   │
927         │may find this enhancement particularly useful for debugging  │
928         │in future labs.                                              │
929         └─────────────────────────────────────────────────────────────┘
931 This completes the lab. Type gmake handin in the lab2 directory to submit your
932 solutions.