软盘和硬盘用于PC的软盘和硬盘分为512个字节区域,称为扇区。 扇区是磁盘的最小传输粒度:每个读取或写入操作必须是一个或多个扇区,并在扇区边界上对齐。 如果磁盘是可引导的,则第一个扇区称为引导扇区,因为这是引导加载程序代码所在的位置。 当BIOS找到可引导的软盘或硬盘时,它将512字节的引导扇区加载到物理地址0x7c00到0x7dff的内存中,然后使用jmp
指令将CS:IP设置为0000:7c00,将控制权传递给引导装载机。 与BIOS加载地址一样,这些地址相当随意 - 但它们是针对PC修复和标准化的。
The ability to boot from a CD-ROM came much later during the evolution of the PC, and as a result the PC architects took the opportunity to rethink the boot process slightly. As a result, the way a modern BIOS boots from a CD-ROM is a bit more complicated (and more powerful). CD-ROMs use a sector size of 2048 bytes instead of 512, and the BIOS can load a much larger boot image from the disk into memory (not just one sector) before transferring control to it. For more information, see the "El Torito" Bootable CD-ROM Format Specification.
MIT 6.828将使用传统的硬盘启动机制,即启动加载程序为512字节。
引导加载程序包含一个汇编语言源文件boot / boot.S
和一个C源文件boot / main.c
将处理器由 real mode
切换为 protected mode
, 使软件能够访问所有的 1MB 以上的物理地址空间。
Protected mode is described briefly in sections 1.2.7 and 1.2.8 of PC Assembly Language, and in great detail in the Intel architecture manuals.
Bootloader通过x86特殊I / O指令直接访问IDE磁盘设备寄存器,从硬盘读取内核。
what the particular I/O instructions here mean, check out the "IDE hard drive controller" section on the 6.828 reference page.
Question
Be able to answer the following questions:
At what point does the processor start executing 32-bit code? What exactly causes the switch from 16- to 32-bit mode?
in boot / boot.S
# Switch from real to protected mode, using a bootstrap GDT
# and segment translation that makes virtual addresses
# identical to their physical addresses, so that the
# effective memory map does not change during the switch.
lgdt gdtdesc
movl %cr0, %eax
orl $CR0_PE_ON, %eax
movl %eax, %cr0
What is the last instruction of the boot loader executed, and what is the first instruction of the kernel it just loaded?
in boot / main.c
// call the entry point from the ELF header
// note: does not return!
((void (*)(void)) (ELFHDR->e_entry))();
in obj/boot/boot.asm
((void (*)(void)) (ELFHDR->e_entry))();
7d6b: ff 15 18 00 01 00 call *0x10018
可以看到跳转至了 0x10018
所存储的值,设置断点至*0x7d6b
(gdb) b *0x7d6b
Breakpoint 1 at 0x7d6b
(gdb) c
Continuing.
The target architecture is assumed to be i386
=> 0x7d6b: call *0x10018
Breakpoint 1, 0x00007d6b in ?? ()
(gdb) si
=> 0x10000c: movw $0x1234,0x472
0x0010000c in ?? ()
可以看到接下来运行的地址为 0x001000c
, 正好是 kernel
的入口地址。
Where is the first instruction of the kernel?
*0x001000c
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?
in boot / main.c
// load each program segment (ignores ph flags)
ph = (struct Proghdr *) ((uint8_t *) ELFHDR + ELFHDR->e_phoff);
eph = ph + ELFHDR->e_phnum;
for (; ph < eph; ph++)
// p_pa is the load address of this segment (as well
// as the physical address)
readseg(ph->p_pa, ph->p_memsz, ph->p_offset);
ph
记录了初始加载位置,eph
记录了加载终点,共同决定了加载的扇区个数
1. Loading the Kernel 加载内核
Review of basis of C programming
Pointers
ELF binary: Executable and Linkable Format ( the ELF specification )
一些相关的 ELF headers (C definition in inc/elf.h
):
.text
: 程序的可执行指令..rodata
: 只读数据,如C编译器生成的ASCII字符串常量。.data
: 程序的初始数据,例如全局变量
Examine the full list of the names, sizes, and link addresses of all the sections in the kernel executable by typing: objdump -h obj/kern/kernel
obj/kern/kernel: file format elf32-i386
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 000019e9 f0100000 00100000 00001000 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .rodata 000006c0 f0101a00 00101a00 00002a00 2**5
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .stab 00003b95 f01020c0 001020c0 000030c0 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .stabstr 00001948 f0105c55 00105c55 00006c55 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .data 00009300 f0108000 00108000 00009000 2**12
CONTENTS, ALLOC, LOAD, DATA
5 .got 00000008 f0111300 00111300 00012300 2**2
CONTENTS, ALLOC, LOAD, DATA
6 .got.plt 0000000c f0111308 00111308 00012308 2**2
CONTENTS, ALLOC, LOAD, DATA
7 .data.rel.local 00001000 f0112000 00112000 00013000 2**12
CONTENTS, ALLOC, LOAD, DATA
8 .data.rel.ro.local 00000044 f0113000 00113000 00014000 2**2
CONTENTS, ALLOC, LOAD, DATA
9 .bss 00000648 f0113060 00113060 00014060 2**5
CONTENTS, ALLOC, LOAD, DATA
10 .comment 0000002b 00000000 00000000 000146a8 2**0
CONTENTS, READONLY
VMA (link address): 该部分期望执行的内存地址
LMA (load address): 该部分在内存中的加载地址 (for .text
)
LMA的定义位置:
We set the link address by passing -Ttext 0x7C00
to the linker in boot/Makefrag
, so the linker will produce the correct memory addresses in the generated code.
# original
The target architecture is assumed to be i8086
[f000:fff0] 0xffff0: ljmp $0xf000,$0xe05b
0x0000fff0 in ?? ()
+ symbol-file obj/kern/kernel
(gdb) si
[f000:e05b] 0xfe05b: cmpl $0x0,%cs:0x6ac8
0x0000e05b in ?? ()
测试改变其定义位置,改变为 $(V)$(LD) $(LDFLAGS) -N -e start -Ttext 0x8C00 -o [email protected] $^
,
make clean && make
, run objdump -h obj/boot/boot.out
obj/boot/boot.out: file format elf32-i386
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000186 00008c00 00008c00 00000074 2**2
CONTENTS, ALLOC, LOAD, CODE
1 .eh_frame 000000a8 00008d88 00008d88 000001fc 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .stab 0000087c 00000000 00000000 000002a4 2**2
CONTENTS, READONLY, DEBUGGING
3 .stabstr 00000925 00000000 00000000 00000b20 2**0
CONTENTS, READONLY, DEBUGGING
4 .comment 0000002b 00000000 00000000 00001445 2**0
CONTENTS, READONLY
但是使用gdb
似乎又正常,不过在退出的时候有一堆报错:
***
*** Now run 'make gdb'.
***
qemu-system-i386 -nographic -drive file=obj/kern/kernel.img,index=0,media=disk,format=raw -serial mon:stdio -gdb tcp::26000 -D qemu.log -S
EAX=00000011 EBX=00000000 ECX=00000000 EDX=00000080
ESI=00000000 EDI=00000000 EBP=00000000 ESP=00006f20
EIP=00007c2d EFL=00000006 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0000 00000000 0000ffff 00009300 DPL=0 DS16 [-WA]
CS =0000 00000000 0000ffff 00009b00 DPL=0 CS16 [-RA]
SS =0000 00000000 0000ffff 00009300 DPL=0 DS16 [-WA]
DS =0000 00000000 0000ffff 00009300 DPL=0 DS16 [-WA]
FS =0000 00000000 0000ffff 00009300 DPL=0 DS16 [-WA]
GS =0000 00000000 0000ffff 00009300 DPL=0 DS16 [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT= 00000000 00000000
IDT= 00000000 000003ff
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
DR6=ffff0ff0 DR7=00000400
EFER=0000000000000000
Triple fault. Halting for inspection via QEMU monitor.
而之前是
***
*** Now run 'make gdb'.
***
qemu-system-i386 -nographic -drive file=obj/kern/kernel.img,index=0,media=disk,format=raw -serial mon:stdio -gdb tcp::26000 -D qemu.log -S
6828 decimal is XXX octal!
entering test_backtrace 5
entering test_backtrace 4
entering test_backtrace 3
entering test_backtrace 2
entering test_backtrace 1
entering test_backtrace 0
leaving test_backtrace 0
leaving test_backtrace 1
leaving test_backtrace 2
leaving test_backtrace 3
leaving test_backtrace 4
leaving test_backtrace 5
Welcome to the JOS kernel monitor!
Type 'help' for a list of commands.
K>
测试内存:
examine memory using GDB's x command. The GDB manual has full details.
the command x/<N>x <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).
前面已经知道kernel
的入口地址为 0x001000c
, bootloader
的地址为 0x7c00
, 这里做一个简单的测试:
- 在
bootloader
载入kernel
前查看0x001000c
的内存信息,可以发现全是初始状态的0 question2
可以知道加载完kernel
后会跳转至kernel
入口,0x001000c
, 此时会发现内存信息已经初始化
The target architecture is assumed to be i8086
[f000:fff0] 0xffff0: ljmp $0xf000,$0xe05b
0x0000fff0 in ?? ()
+ symbol-file obj/kern/kernel
(gdb) b *0x7c00
Breakpoint 1 at 0x7c00
(gdb) c
Continuing.
[ 0:7c00] => 0x7c00: cli
Breakpoint 1, 0x00007c00 in ?? ()
(gdb) x /8wx 0x00100000
0x100000: 0x00000000 0x00000000 0x00000000 0x00000000
0x100010: 0x00000000 0x00000000 0x00000000 0x00000000
(gdb) b*0x10000c
Breakpoint 2 at 0x10000c
(gdb) c
Continuing.
The target architecture is assumed to be i386
=> 0x10000c: movw $0x1234,0x472
Breakpoint 2, 0x0010000c in ?? ()
(gdb) x /8wx 0x00100000
0x100000: 0x1badb002 0x00000000 0xe4524ffe 0x7205c766
0x100010: 0x34000004 0x2000b812 0x220f0011 0xc0200fd8
同时在 obj/kern/kernel.asm
文件最开始中可以发现:
.globl entry
entry:
movw $0x1234,0x472 # warm boot
f0100000: 02 b0 ad 1b 00 00 add 0x1bad(%eax),%dh
f0100006: 00 00 add %al,(%eax)
f0100008: fe 4f 52 decb 0x52(%edi)
f010000b: e4 .byte 0xe4
其汇编指令恰好对应 kernel
入口内存中的数据